aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Gordon <msg555@gmail.com>2011-10-07 16:02:26 -0400
committerMark Gordon <msg555@gmail.com>2011-10-07 16:02:26 -0400
commitdd56456c9780576206ae45e55904bdcc2d87cc1b (patch)
tree7a4302ad32c7034afa77303a8b6d6e96853c095d
downloadPowerTutor-dd56456c9780576206ae45e55904bdcc2d87cc1b.tar.gz
initial commit
-rw-r--r--AndroidManifest.xml95
-rw-r--r--LICENCE16
-rw-r--r--LogUploaderUmich.java224
-rw-r--r--Makefile39
-rw-r--r--android-9.jarbin0 -> 8407959 bytes
-rw-r--r--debug.keystorebin0 -> 1266 bytes
-rw-r--r--default.properties11
-rw-r--r--jni/Android.mk9
-rw-r--r--jni/bindings.cpp81
-rw-r--r--libs/achartengine-0.7.0.jarbin0 -> 84361 bytes
-rwxr-xr-xlibs/armeabi/bindings.sobin0 -> 5200 bytes
-rwxr-xr-xobj/local/armeabi/bindings.sobin0 -> 49756 bytes
-rw-r--r--obj/local/armeabi/libstdc++.a1
-rw-r--r--obj/local/armeabi/objs/bindings-jni/bindings.obin0 -> 53848 bytes
-rw-r--r--obj/local/armeabi/objs/bindings-jni/bindings.o.d182
-rw-r--r--obj/local/armeabi/objs/bindings/bindings.obin0 -> 53848 bytes
-rw-r--r--obj/local/armeabi/objs/bindings/bindings.o.d182
-rw-r--r--res/drawable/background.pngbin0 -> 71102 bytes
-rw-r--r--res/drawable/help.pngbin0 -> 2051 bytes
-rw-r--r--res/drawable/icon.pngbin0 -> 834 bytes
-rw-r--r--res/drawable/level.xml12
-rw-r--r--res/drawable/level_1.pngbin0 -> 252 bytes
-rw-r--r--res/drawable/level_2.pngbin0 -> 256 bytes
-rw-r--r--res/drawable/level_3.pngbin0 -> 257 bytes
-rw-r--r--res/drawable/level_4.pngbin0 -> 254 bytes
-rw-r--r--res/drawable/level_5.pngbin0 -> 254 bytes
-rw-r--r--res/drawable/level_6.pngbin0 -> 248 bytes
-rw-r--r--res/drawable/level_7.pngbin0 -> 248 bytes
-rw-r--r--res/drawable/level_8.pngbin0 -> 247 bytes
-rw-r--r--res/drawable/level_9.pngbin0 -> 248 bytes
-rw-r--r--res/drawable/line.pngbin0 -> 180 bytes
-rw-r--r--res/drawable/power_off.pngbin0 -> 3037 bytes
-rw-r--r--res/drawable/power_on.pngbin0 -> 3353 bytes
-rw-r--r--res/drawable/time.xml16
-rw-r--r--res/drawable/time_0.pngbin0 -> 354 bytes
-rw-r--r--res/drawable/time_1.pngbin0 -> 478 bytes
-rw-r--r--res/drawable/time_10.pngbin0 -> 482 bytes
-rw-r--r--res/drawable/time_11.pngbin0 -> 374 bytes
-rw-r--r--res/drawable/time_12.pngbin0 -> 389 bytes
-rw-r--r--res/drawable/time_2.pngbin0 -> 524 bytes
-rw-r--r--res/drawable/time_3.pngbin0 -> 482 bytes
-rw-r--r--res/drawable/time_4.pngbin0 -> 501 bytes
-rw-r--r--res/drawable/time_5.pngbin0 -> 326 bytes
-rw-r--r--res/drawable/time_6.pngbin0 -> 432 bytes
-rw-r--r--res/drawable/time_7.pngbin0 -> 460 bytes
-rw-r--r--res/drawable/time_8.pngbin0 -> 379 bytes
-rw-r--r--res/drawable/time_9.pngbin0 -> 397 bytes
-rw-r--r--res/drawable/widget_bg.pngbin0 -> 2850 bytes
-rw-r--r--res/layout/help.xml68
-rw-r--r--res/layout/main.xml78
-rw-r--r--res/layout/misc_item_layout.xml42
-rw-r--r--res/layout/misc_layout.xml19
-rw-r--r--res/layout/power_tabs.xml21
-rw-r--r--res/layout/widget_configure.xml33
-rw-r--r--res/layout/widget_item_layout.xml27
-rw-r--r--res/layout/widget_layout.xml42
-rw-r--r--res/values/arrays.xml30
-rw-r--r--res/values/strings.xml90
-rw-r--r--res/xml/preferences.xml12
-rw-r--r--res/xml/viewer_preferences.xml15
-rw-r--r--res/xml/widget_info.xml8
-rw-r--r--src/edu/umich/PowerTutor/PowerNotifications.aidl70
-rw-r--r--src/edu/umich/PowerTutor/components/Audio.java188
-rw-r--r--src/edu/umich/PowerTutor/components/CPU.java358
-rw-r--r--src/edu/umich/PowerTutor/components/GPS.java456
-rw-r--r--src/edu/umich/PowerTutor/components/LCD.java179
-rw-r--r--src/edu/umich/PowerTutor/components/OLED.java311
-rw-r--r--src/edu/umich/PowerTutor/components/PowerComponent.java142
-rw-r--r--src/edu/umich/PowerTutor/components/Sensors.java217
-rw-r--r--src/edu/umich/PowerTutor/components/Threeg.java398
-rw-r--r--src/edu/umich/PowerTutor/components/Wifi.java418
-rw-r--r--src/edu/umich/PowerTutor/phone/DreamConstants.java216
-rw-r--r--src/edu/umich/PowerTutor/phone/DreamPowerCalculator.java163
-rw-r--r--src/edu/umich/PowerTutor/phone/PassionConstants.java160
-rw-r--r--src/edu/umich/PowerTutor/phone/PassionPowerCalculator.java56
-rw-r--r--src/edu/umich/PowerTutor/phone/PhoneConstants.java150
-rw-r--r--src/edu/umich/PowerTutor/phone/PhonePowerCalculator.java48
-rw-r--r--src/edu/umich/PowerTutor/phone/PhoneSelector.java204
-rw-r--r--src/edu/umich/PowerTutor/phone/PowerFunction.java26
-rw-r--r--src/edu/umich/PowerTutor/phone/SapphireConstants.java123
-rw-r--r--src/edu/umich/PowerTutor/phone/SapphirePowerCalculator.java42
-rw-r--r--src/edu/umich/PowerTutor/service/ICounterService.aidl79
-rw-r--r--src/edu/umich/PowerTutor/service/IterationData.java76
-rw-r--r--src/edu/umich/PowerTutor/service/LogUploader.java68
-rw-r--r--src/edu/umich/PowerTutor/service/PowerData.java48
-rw-r--r--src/edu/umich/PowerTutor/service/PowerEstimator.java580
-rw-r--r--src/edu/umich/PowerTutor/service/UMLoggerService.java394
-rw-r--r--src/edu/umich/PowerTutor/service/UidInfo.java65
-rw-r--r--src/edu/umich/PowerTutor/ui/EditPreferences.java32
-rw-r--r--src/edu/umich/PowerTutor/ui/Help.java49
-rw-r--r--src/edu/umich/PowerTutor/ui/MiscView.java478
-rw-r--r--src/edu/umich/PowerTutor/ui/PowerPie.java295
-rw-r--r--src/edu/umich/PowerTutor/ui/PowerTabs.java63
-rw-r--r--src/edu/umich/PowerTutor/ui/PowerTop.java428
-rw-r--r--src/edu/umich/PowerTutor/ui/PowerViewer.java343
-rw-r--r--src/edu/umich/PowerTutor/ui/StartupReceiver.java41
-rw-r--r--src/edu/umich/PowerTutor/ui/UMLogger.java355
-rw-r--r--src/edu/umich/PowerTutor/ui/ViewerPreferences.java32
-rw-r--r--src/edu/umich/PowerTutor/util/BatteryStats.java214
-rw-r--r--src/edu/umich/PowerTutor/util/Counter.java118
-rw-r--r--src/edu/umich/PowerTutor/util/ForegroundDetector.java114
-rw-r--r--src/edu/umich/PowerTutor/util/HexEncode.java41
-rw-r--r--src/edu/umich/PowerTutor/util/HistoryBuffer.java136
-rw-r--r--src/edu/umich/PowerTutor/util/NativeLoader.java45
-rw-r--r--src/edu/umich/PowerTutor/util/NotificationService.java354
-rw-r--r--src/edu/umich/PowerTutor/util/Recycler.java52
-rw-r--r--src/edu/umich/PowerTutor/util/SystemInfo.java573
-rw-r--r--src/edu/umich/PowerTutor/widget/Configure.java163
-rw-r--r--src/edu/umich/PowerTutor/widget/DataSource.java341
-rw-r--r--src/edu/umich/PowerTutor/widget/DataSourceConfigure.java134
-rw-r--r--src/edu/umich/PowerTutor/widget/PowerWidget.java152
111 files changed, 11141 insertions, 0 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 0000000..720938e
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,95 @@
+<?xml version="1.1" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="edu.umich.PowerTutor"
+ android:versionCode="13" android:versionName="1.4">
+ <application android:icon="@drawable/icon" android:label="@string/app_name">
+ <activity android:name=".ui.UMLogger"
+ android:label="@string/app_name"
+ android:screenOrientation="portrait" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".ui.Help" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".ui.PowerViewer" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".ui.PowerTop"
+ android:label="Power Top" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".ui.PowerPie" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".ui.MiscView" >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".ui.PowerTabs"
+ android:theme="@android:style/Theme.NoTitleBar">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".ui.EditPreferences"
+ android:label="PowerTutor Options">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".ui.ViewerPreferences"
+ android:label="PowerTutor History Options">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".widget.Configure"
+ android:label="Configure PowerTutor Widget" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+ <activity android:name=".widget.DataSourceConfigure"
+ android:label="Configure Data Source">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+ <receiver android:name=".ui.StartupReceiver">
+ <intent-filter>
+ <action android:name="android.intent.action.BOOT_COMPLETED" />
+ <category android:name="android.intent.category.HOME" />
+ </intent-filter>
+ </receiver>
+ <service android:name=".service.UMLoggerService"></service>
+ <receiver android:name=".widget.PowerWidget" >
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+ <meta-data android:name="android.appwidget.provider"
+ android:resource="@xml/widget_info" />
+ </receiver>
+ </application>
+
+
+
+ <uses-sdk android:minSdkVersion="3" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+ <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+ <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+</manifest>
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..d3bb00c
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,16 @@
+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
diff --git a/LogUploaderUmich.java b/LogUploaderUmich.java
new file mode 100644
index 0000000..45045ce
--- /dev/null
+++ b/LogUploaderUmich.java
@@ -0,0 +1,224 @@
+package edu.umich.PowerTutor.service;
+
+import edu.umich.PowerTutor.ui.UMLogger;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Build;
+import android.os.PowerManager;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketTimeoutException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.zip.DeflaterOutputStream;
+
+/* This class is responsible for all of the policy decisions on when to actually
+ * send log information back to our log collecting servers and is also
+ * responsible for actually sending the data should it decide that it is
+ * appropriate.
+ */
+public class LogUploader {
+ private static final String TAG = "LogUploader";
+
+ public static final String UPLOAD_FILE = "PowerTrace_Upload.log";
+
+ private static final long NONE_LOG_LENGTH = 1 << 20; // 1 MiB
+ private static final long WIFI_LOG_LENGTH = 1 << 17; // 128 KiB
+ private static final long THREEG_LOG_LENGTH = 1 << 19; // 512 KiB
+
+ private static final int CONNECTION_NONE = 0;
+ private static final int CONNECTION_WIFI = 1;
+ private static final int CONNECTION_3G = 2;
+
+ private boolean plugged;
+
+ private File logFile;
+ private ConnectivityManager connectivityManager;
+ private TelephonyManager telephonyManager;
+
+ private Thread uploadThread;
+
+ public LogUploader(Context context) {
+ telephonyManager = (TelephonyManager)context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ connectivityManager = (ConnectivityManager)context.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ logFile = context.getFileStreamPath(UPLOAD_FILE);
+ }
+
+ public synchronized boolean shouldUpload() {
+ switch(connectionAvailable()) {
+ case CONNECTION_WIFI:
+ return plugged && logFile.length() > WIFI_LOG_LENGTH;
+ case CONNECTION_3G:
+ return plugged && logFile.length() > THREEG_LOG_LENGTH;
+ default: // CONNECTION_NONE
+ return logFile.length() > NONE_LOG_LENGTH;
+ }
+ }
+
+ public synchronized void plug(boolean plugged) {
+ this.plugged = plugged;
+ }
+
+ private int connectionAvailable() {
+ /* TODO: Maybe we should only send data when the device is plugged in.
+ */
+ NetworkInfo info = connectivityManager.getActiveNetworkInfo();
+ if(info == null || !connectivityManager.getBackgroundDataSetting()) {
+ return CONNECTION_NONE;
+ }
+ int netType = info.getType();
+ int netSubtype = info.getSubtype();
+ if (netType == ConnectivityManager.TYPE_WIFI) {
+ return info.isConnected() ? CONNECTION_WIFI : CONNECTION_NONE;
+ } else if (netType == ConnectivityManager.TYPE_MOBILE
+ && netSubtype == TelephonyManager.NETWORK_TYPE_UMTS
+ && !telephonyManager.isNetworkRoaming()) {
+ return info.isConnected() ? CONNECTION_3G : CONNECTION_NONE;
+ }
+ return CONNECTION_NONE;
+ }
+
+ public void upload(String origFile) {
+ if(new File(origFile).renameTo(logFile)) {
+ interrupt();
+ uploadThread = new Thread() {
+ public void run() {
+ long runID = System.currentTimeMillis();
+ for(int iter = 1; !interrupted(); iter++) {
+ if(send(runID)) {
+ break;
+ }
+ if(iter > 12) iter = 12; // The max wait is a little over 1 hour.
+ Log.i(TAG, "Failed to send log. Will try again in " + (1 << iter) +
+ " seconds");
+ try {
+ do {
+ sleep(1000 * (1 << iter)); // Sleep for 2^iter seconds.
+ } while(connectionAvailable() == CONNECTION_NONE);
+ } catch(InterruptedException e) {
+ break;
+ }
+ }
+ }
+ };
+ uploadThread.start();
+ } else {
+ Log.w(TAG, "Failed to move log file before sending");
+ }
+ }
+
+ public boolean isUploading() {
+ return uploadThread != null && uploadThread.isAlive();
+ }
+
+ public void interrupt() {
+ if(uploadThread != null) {
+ uploadThread.interrupt();
+ }
+ }
+
+ public void join() throws InterruptedException {
+ if(uploadThread != null) {
+ uploadThread.join();
+ }
+ }
+
+ public boolean send(long runID) {
+ Log.i(TAG, "Sending log data");
+ Socket s = new Socket();
+ try {
+ s.setSoTimeout(4000);
+ s.connect(new InetSocketAddress(UMLogger.SERVER_IP, UMLogger.SERVER_PORT),
+ 15000);
+ } catch(IOException e) {
+ /* Failed to connect to server. Try again later.
+ */
+ return false;
+ }
+
+ try {
+ BufferedInputStream in = new BufferedInputStream(
+ new FileInputStream(logFile), 1024);
+ BufferedOutputStream sockOut = new BufferedOutputStream(
+ s.getOutputStream(), 1024);
+
+ /* Write the prefix string to the server. */
+ sockOut.write(getPrefix(runID, logFile.length()));
+ sockOut.write(0);
+
+ /* Write the log file to the server. */
+ byte[] buf = new byte[1024];
+ while(true) {
+ int sz = in.read(buf, 0, buf.length);
+ if(sz == -1) break;
+ sockOut.write(buf, 0, sz);
+ }
+ sockOut.flush();
+ int response = s.getInputStream().read();
+ in.close();
+ s.close();
+
+ if(response != 0) {
+ Log.w(TAG, "Log data not accepted by server");
+ }
+ } catch(SocketTimeoutException e) {
+ /* Connection trouble with server. Try again later.
+ */
+ return false;
+ } catch(IOException e) {
+ Log.w(TAG, "Unexpected exception sending log. Dropping log data");
+ e.printStackTrace();
+ }
+ logFile.delete();
+ return true;
+ }
+
+ private byte[] getPrefix(long runID, long payloadLength) {
+ String deviceID = telephonyManager.getDeviceId();
+ return (UMLogger.CURRENT_VERSION + '|' + sanatize(Build.DEVICE) + '|' +
+ getMD5(deviceID) + "|" + payloadLength).getBytes();
+ }
+
+ /* Just strip out any | characters present. Normal DEVICE strings shouldn't
+ * have a | but this string can be set by anyone so we should treat it as
+ * adversarial.
+ */
+ private String sanatize(String s) {
+ StringBuffer buf = new StringBuffer();
+ for(int i = 0; i < s.length(); i++) {
+ if(s.charAt(i) != '|') {
+ buf.append(s.charAt(i));
+ }
+ }
+ return buf.toString();
+ }
+
+ private String getMD5(String s){
+ MessageDigest m = null;
+ try {
+ m = MessageDigest.getInstance("MD5");
+ } catch (NoSuchAlgorithmException e) {
+ // Well this sucks...
+ e.printStackTrace();
+ return "nohash";
+ }
+ m.update(s.getBytes(), 0, s.length());
+ return new BigInteger(1, m.digest()).toString(16);
+ }
+}
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..6d618fa
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+all: package
+
+ANDROID_LIB=android-9.jar
+CLASSPATH=$(ANDROID_LIB):libs/achartengine-0.7.0.jar:libs/com.artfulbits.aiCharts.jar
+
+genres:
+ mkdir -p gen bin
+ aapt package -m -J gen -M AndroidManifest.xml -S res -I $(ANDROID_LIB)
+
+aidl:
+ find src/ -type f | \
+ grep '\.aidl$$' | \
+ xargs -n 1 aidl -Isrc -I$(ANDROID_LIB) -ogen
+
+gen: genres aidl
+
+compile: gen
+ mkdir -p bin
+ find src/ gen/ -type f | \
+ grep '\.java$$' | \
+ xargs javac -cp $(CLASSPATH) -d bin
+ ndk-build
+
+dex: compile
+ dx --dex --output=bin/classes.dex bin/ libs/
+
+package: dex
+ aapt package -M AndroidManifest.xml -A assets -S res \
+ -F bin/PowerTutor.apk -I $(ANDROID_LIB)
+ cd bin; zip PowerTutor.apk classes.dex
+ zip bin/PowerTutor.apk -r libs -i \*.so
+ jarsigner -storepass android -keystore debug.keystore \
+ bin/PowerTutor.apk androiddebugkey
+
+install: package
+ adb install bin/PowerTutor.apk
+
+clean:
+ rm -rf bin/ gen/
diff --git a/android-9.jar b/android-9.jar
new file mode 100644
index 0000000..79c7ea4
--- /dev/null
+++ b/android-9.jar
Binary files differ
diff --git a/debug.keystore b/debug.keystore
new file mode 100644
index 0000000..74dc10d
--- /dev/null
+++ b/debug.keystore
Binary files differ
diff --git a/default.properties b/default.properties
new file mode 100644
index 0000000..bc708b0
--- /dev/null
+++ b/default.properties
@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-3
diff --git a/jni/Android.mk b/jni/Android.mk
new file mode 100644
index 0000000..262331c
--- /dev/null
+++ b/jni/Android.mk
@@ -0,0 +1,9 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAN_VARS)
+
+LOCAL_MODULE_FILENAME := bindings
+LOCAL_MODULE := bindings
+LOCAL_SRC_FILES := bindings.cpp
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/jni/bindings.cpp b/jni/bindings.cpp
new file mode 100644
index 0000000..2cced7a
--- /dev/null
+++ b/jni/bindings.cpp
@@ -0,0 +1,81 @@
+#include <jni.h>
+#include <stdio.h>
+#include <linux/fb.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <stdlib.h>
+
+extern "C" {
+JNIEXPORT jdouble JNICALL
+Java_edu_umich_PowerTutor_components_OLED_getScreenPixPower(
+ JNIEnv * env, jobject thiz, jdouble rcoef, jdouble gcoef,
+ jdouble bcoef, jdouble modul_coef);
+}
+
+#define NUMBER_OF_SAMPLES 500
+
+static int fd = -2;
+static struct fb_fix_screeninfo screeninfo;
+static unsigned char* buf;
+
+static unsigned int samples[NUMBER_OF_SAMPLES];
+
+JNIEXPORT jdouble JNICALL
+Java_edu_umich_PowerTutor_components_OLED_getScreenPixPower(
+ JNIEnv * env, jobject thiz, jdouble rcoef, jdouble gcoef,
+ jdouble bcoef, jdouble modul_coef) {
+ if(fd == -1) {
+ return (jdouble)-1.0;
+ } else if(fd == -2) {
+ fd = open("/dev/graphics/fb0", O_RDWR);
+ if(fd == -1) {
+ fd = open("/dev/fb0", O_RDWR);
+ }
+ if(fd == -1) {
+ return (jdouble)-1.0;
+ }
+ if(ioctl(fd, FBIOGET_FSCREENINFO, &screeninfo)) {
+ close(fd);
+ fd = -1;
+ return (jdouble)-1.0;
+ }
+ buf = (unsigned char*)mmap(NULL, screeninfo.smem_len, PROT_READ,
+ MAP_SHARED, fd, 0);
+ if(buf == (unsigned char*)-1) {
+ close(fd);
+ fd = -1;
+ return (jdouble)-1.0;
+ }
+ int range = screeninfo.smem_len / 12;
+ srand(555);
+ for(int i = 0; i < NUMBER_OF_SAMPLES; i++) {
+ int a = range * i / NUMBER_OF_SAMPLES;
+ int b = range * (i + 1) / NUMBER_OF_SAMPLES;
+ if(b <= a + 1) {
+ samples[i] = a;
+ } else {
+ samples[i] = a + rand() % (b - a);
+ }
+ }
+ }
+ jdouble pixPower = 0.0;
+ for(int i = 0; i < NUMBER_OF_SAMPLES; i++) {
+ int x = 4 * samples[i];
+ int r = buf[x];
+ int g = buf[x + 1];
+ int b = buf[x + 2];
+
+ /* Calculate the power usage of this one pixel if it were at full
+ * brightness. Linearly scale by brightness to get true power
+ * consumption. To calculate whole screen compute average of sampled
+ * region and multiply by number of pixels.
+ */
+ int modul_val = r + g + b;
+ pixPower += rcoef * (r * r) + gcoef * (g * g) + bcoef * (b * b) -
+ modul_coef * (modul_val * modul_val);
+ }
+ return pixPower;
+}
diff --git a/libs/achartengine-0.7.0.jar b/libs/achartengine-0.7.0.jar
new file mode 100644
index 0000000..f9e3b83
--- /dev/null
+++ b/libs/achartengine-0.7.0.jar
Binary files differ
diff --git a/libs/armeabi/bindings.so b/libs/armeabi/bindings.so
new file mode 100755
index 0000000..0b0b28b
--- /dev/null
+++ b/libs/armeabi/bindings.so
Binary files differ
diff --git a/obj/local/armeabi/bindings.so b/obj/local/armeabi/bindings.so
new file mode 100755
index 0000000..e3a5f2e
--- /dev/null
+++ b/obj/local/armeabi/bindings.so
Binary files differ
diff --git a/obj/local/armeabi/libstdc++.a b/obj/local/armeabi/libstdc++.a
new file mode 100644
index 0000000..8b277f0
--- /dev/null
+++ b/obj/local/armeabi/libstdc++.a
@@ -0,0 +1 @@
+!<arch>
diff --git a/obj/local/armeabi/objs/bindings-jni/bindings.o b/obj/local/armeabi/objs/bindings-jni/bindings.o
new file mode 100644
index 0000000..d085271
--- /dev/null
+++ b/obj/local/armeabi/objs/bindings-jni/bindings.o
Binary files differ
diff --git a/obj/local/armeabi/objs/bindings-jni/bindings.o.d b/obj/local/armeabi/objs/bindings-jni/bindings.o.d
new file mode 100644
index 0000000..1e5bdc1
--- /dev/null
+++ b/obj/local/armeabi/objs/bindings-jni/bindings.o.d
@@ -0,0 +1,182 @@
+/home/msg555/PowerTutor/obj/local/armeabi/objs/bindings-jni/bindings.o: \
+ /home/msg555/PowerTutor/jni/bindings.cpp \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/jni.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdio.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/cdefs.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/cdefs_elf.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/_types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/_types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdint.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/posix_types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/stddef.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/compiler.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/posix_types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/kernel.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/sysmacros.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/fb.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/ioctl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/ioctl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/ioctl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/ioctl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/ioctls.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/termbits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/ioctl_compat.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/stat.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/time.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/time.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/stat.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/endian.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/endian.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/fcntl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/fcntl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/fcntl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/fcntl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/unistd.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/select.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/signal.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/limits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/limits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/limits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/internal_types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/limits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/syslimits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/string.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/malloc.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/signal.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/signal.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/siginfo.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/siginfo.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/sysconf.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/capability.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/pathconf.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/mman.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/mman.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/mman.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/page.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdlib.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/alloca.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/strings.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/memory.h
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/jni.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdio.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/cdefs.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/cdefs_elf.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/_types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/_types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdint.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/posix_types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/stddef.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/compiler.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/posix_types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/kernel.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/sysmacros.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/fb.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/ioctl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/ioctl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/ioctl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/ioctl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/ioctls.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/termbits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/ioctl_compat.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/stat.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/time.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/time.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/stat.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/endian.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/endian.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/fcntl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/fcntl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/fcntl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/fcntl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/unistd.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/select.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/signal.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/limits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/limits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/limits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/internal_types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/limits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/syslimits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/string.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/malloc.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/signal.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/signal.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/siginfo.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/siginfo.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/sysconf.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/capability.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/pathconf.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/mman.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/mman.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/mman.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/page.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdlib.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/alloca.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/strings.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/memory.h:
diff --git a/obj/local/armeabi/objs/bindings/bindings.o b/obj/local/armeabi/objs/bindings/bindings.o
new file mode 100644
index 0000000..d085271
--- /dev/null
+++ b/obj/local/armeabi/objs/bindings/bindings.o
Binary files differ
diff --git a/obj/local/armeabi/objs/bindings/bindings.o.d b/obj/local/armeabi/objs/bindings/bindings.o.d
new file mode 100644
index 0000000..82e45db
--- /dev/null
+++ b/obj/local/armeabi/objs/bindings/bindings.o.d
@@ -0,0 +1,182 @@
+/home/msg555/PowerTutor/obj/local/armeabi/objs/bindings/bindings.o: \
+ /home/msg555/PowerTutor/jni/bindings.cpp \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/jni.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdio.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/cdefs.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/cdefs_elf.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/_types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/_types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdint.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/posix_types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/stddef.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/compiler.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/posix_types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/kernel.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/sysmacros.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/fb.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/ioctl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/ioctl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/ioctl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/ioctl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/ioctls.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/termbits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/ioctl_compat.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/stat.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/time.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/time.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/stat.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/endian.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/endian.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/fcntl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/fcntl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/fcntl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/fcntl.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/unistd.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/select.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/signal.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/limits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/limits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/limits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/internal_types.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/limits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/syslimits.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/string.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/malloc.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/signal.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/signal.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/siginfo.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/siginfo.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/sysconf.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/capability.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/pathconf.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/mman.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/mman.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/mman.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/page.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdlib.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/alloca.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/strings.h \
+ /home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/memory.h
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/jni.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdio.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/cdefs.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/cdefs_elf.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/_types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/_types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdint.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/posix_types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/stddef.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/compiler.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/posix_types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/kernel.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/sysmacros.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/fb.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/ioctl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/ioctl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/ioctl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/ioctl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/ioctls.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/termbits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/ioctl_compat.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/stat.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/time.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/time.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/stat.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/endian.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/endian.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/fcntl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/fcntl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/fcntl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/fcntl.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/unistd.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/select.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/signal.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/limits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/limits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/limits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/internal_types.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/machine/limits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/syslimits.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/string.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/malloc.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/signal.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/signal.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/siginfo.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/siginfo.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/sysconf.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/linux/capability.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/pathconf.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/sys/mman.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/mman.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm-generic/mman.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/asm/page.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/stdlib.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/alloca.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/strings.h:
+
+/home/msg555/android/android-ndk-r6/platforms/android-3/arch-arm/usr/include/memory.h:
diff --git a/res/drawable/background.png b/res/drawable/background.png
new file mode 100644
index 0000000..3ce01a1
--- /dev/null
+++ b/res/drawable/background.png
Binary files differ
diff --git a/res/drawable/help.png b/res/drawable/help.png
new file mode 100644
index 0000000..c97dc38
--- /dev/null
+++ b/res/drawable/help.png
Binary files differ
diff --git a/res/drawable/icon.png b/res/drawable/icon.png
new file mode 100644
index 0000000..51ba794
--- /dev/null
+++ b/res/drawable/icon.png
Binary files differ
diff --git a/res/drawable/level.xml b/res/drawable/level.xml
new file mode 100644
index 0000000..7c87f58
--- /dev/null
+++ b/res/drawable/level.xml
@@ -0,0 +1,12 @@
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+ android:id="@+id/l1"
+ <item android:maxLevel="0" android:drawable="@drawable/level_1" />
+ <item android:maxLevel="1" android:drawable="@drawable/level_2" />
+ <item android:maxLevel="2" android:drawable="@drawable/level_3" />
+ <item android:maxLevel="3" android:drawable="@drawable/level_4" />
+ <item android:maxLevel="4" android:drawable="@drawable/level_5" />
+ <item android:maxLevel="5" android:drawable="@drawable/level_6" />
+ <item android:maxLevel="6" android:drawable="@drawable/level_7" />
+ <item android:maxLevel="7" android:drawable="@drawable/level_8" />
+ <item android:maxLevel="8" android:drawable="@drawable/level_9" />
+ </level-list>
diff --git a/res/drawable/level_1.png b/res/drawable/level_1.png
new file mode 100644
index 0000000..3c2598b
--- /dev/null
+++ b/res/drawable/level_1.png
Binary files differ
diff --git a/res/drawable/level_2.png b/res/drawable/level_2.png
new file mode 100644
index 0000000..6e6c113
--- /dev/null
+++ b/res/drawable/level_2.png
Binary files differ
diff --git a/res/drawable/level_3.png b/res/drawable/level_3.png
new file mode 100644
index 0000000..9fa1e5d
--- /dev/null
+++ b/res/drawable/level_3.png
Binary files differ
diff --git a/res/drawable/level_4.png b/res/drawable/level_4.png
new file mode 100644
index 0000000..b412871
--- /dev/null
+++ b/res/drawable/level_4.png
Binary files differ
diff --git a/res/drawable/level_5.png b/res/drawable/level_5.png
new file mode 100644
index 0000000..1aabb93
--- /dev/null
+++ b/res/drawable/level_5.png
Binary files differ
diff --git a/res/drawable/level_6.png b/res/drawable/level_6.png
new file mode 100644
index 0000000..412afbe
--- /dev/null
+++ b/res/drawable/level_6.png
Binary files differ
diff --git a/res/drawable/level_7.png b/res/drawable/level_7.png
new file mode 100644
index 0000000..efc3d9c
--- /dev/null
+++ b/res/drawable/level_7.png
Binary files differ
diff --git a/res/drawable/level_8.png b/res/drawable/level_8.png
new file mode 100644
index 0000000..69b59bb
--- /dev/null
+++ b/res/drawable/level_8.png
Binary files differ
diff --git a/res/drawable/level_9.png b/res/drawable/level_9.png
new file mode 100644
index 0000000..bedb895
--- /dev/null
+++ b/res/drawable/level_9.png
Binary files differ
diff --git a/res/drawable/line.png b/res/drawable/line.png
new file mode 100644
index 0000000..c3f3217
--- /dev/null
+++ b/res/drawable/line.png
Binary files differ
diff --git a/res/drawable/power_off.png b/res/drawable/power_off.png
new file mode 100644
index 0000000..2f120c6
--- /dev/null
+++ b/res/drawable/power_off.png
Binary files differ
diff --git a/res/drawable/power_on.png b/res/drawable/power_on.png
new file mode 100644
index 0000000..1ca930d
--- /dev/null
+++ b/res/drawable/power_on.png
Binary files differ
diff --git a/res/drawable/time.xml b/res/drawable/time.xml
new file mode 100644
index 0000000..896640f
--- /dev/null
+++ b/res/drawable/time.xml
@@ -0,0 +1,16 @@
+<level-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:maxLevel="0" android:drawable="@drawable/level_1" />
+ <item android:maxLevel="1" android:drawable="@drawable/time_0" />
+ <item android:maxLevel="2" android:drawable="@drawable/time_1" />
+ <item android:maxLevel="3" android:drawable="@drawable/time_2" />
+ <item android:maxLevel="4" android:drawable="@drawable/time_3" />
+ <item android:maxLevel="5" android:drawable="@drawable/time_4" />
+ <item android:maxLevel="6" android:drawable="@drawable/time_5" />
+ <item android:maxLevel="7" android:drawable="@drawable/time_6" />
+ <item android:maxLevel="8" android:drawable="@drawable/time_7" />
+ <item android:maxLevel="9" android:drawable="@drawable/time_8" />
+ <item android:maxLevel="10" android:drawable="@drawable/time_9" />
+ <item android:maxLevel="11" android:drawable="@drawable/time_10" />
+ <item android:maxLevel="12" android:drawable="@drawable/time_11" />
+ <item android:maxLevel="13" android:drawable="@drawable/time_12" />
+</level-list>
diff --git a/res/drawable/time_0.png b/res/drawable/time_0.png
new file mode 100644
index 0000000..566f3a7
--- /dev/null
+++ b/res/drawable/time_0.png
Binary files differ
diff --git a/res/drawable/time_1.png b/res/drawable/time_1.png
new file mode 100644
index 0000000..12fda18
--- /dev/null
+++ b/res/drawable/time_1.png
Binary files differ
diff --git a/res/drawable/time_10.png b/res/drawable/time_10.png
new file mode 100644
index 0000000..b87108f
--- /dev/null
+++ b/res/drawable/time_10.png
Binary files differ
diff --git a/res/drawable/time_11.png b/res/drawable/time_11.png
new file mode 100644
index 0000000..39ecb69
--- /dev/null
+++ b/res/drawable/time_11.png
Binary files differ
diff --git a/res/drawable/time_12.png b/res/drawable/time_12.png
new file mode 100644
index 0000000..2f5fa6a
--- /dev/null
+++ b/res/drawable/time_12.png
Binary files differ
diff --git a/res/drawable/time_2.png b/res/drawable/time_2.png
new file mode 100644
index 0000000..fa328e6
--- /dev/null
+++ b/res/drawable/time_2.png
Binary files differ
diff --git a/res/drawable/time_3.png b/res/drawable/time_3.png
new file mode 100644
index 0000000..d03b03b
--- /dev/null
+++ b/res/drawable/time_3.png
Binary files differ
diff --git a/res/drawable/time_4.png b/res/drawable/time_4.png
new file mode 100644
index 0000000..9fab40b
--- /dev/null
+++ b/res/drawable/time_4.png
Binary files differ
diff --git a/res/drawable/time_5.png b/res/drawable/time_5.png
new file mode 100644
index 0000000..c12a082
--- /dev/null
+++ b/res/drawable/time_5.png
Binary files differ
diff --git a/res/drawable/time_6.png b/res/drawable/time_6.png
new file mode 100644
index 0000000..1493b38
--- /dev/null
+++ b/res/drawable/time_6.png
Binary files differ
diff --git a/res/drawable/time_7.png b/res/drawable/time_7.png
new file mode 100644
index 0000000..627ce68
--- /dev/null
+++ b/res/drawable/time_7.png
Binary files differ
diff --git a/res/drawable/time_8.png b/res/drawable/time_8.png
new file mode 100644
index 0000000..97ced20
--- /dev/null
+++ b/res/drawable/time_8.png
Binary files differ
diff --git a/res/drawable/time_9.png b/res/drawable/time_9.png
new file mode 100644
index 0000000..c87864e
--- /dev/null
+++ b/res/drawable/time_9.png
Binary files differ
diff --git a/res/drawable/widget_bg.png b/res/drawable/widget_bg.png
new file mode 100644
index 0000000..1f94351
--- /dev/null
+++ b/res/drawable/widget_bg.png
Binary files differ
diff --git a/res/layout/help.xml b/res/layout/help.xml
new file mode 100644
index 0000000..5746d8f
--- /dev/null
+++ b/res/layout/help.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/textAreaScroller"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_x="0px"
+ android:layout_y="0px"
+ android:background = "@drawable/help"
+ >
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation ="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ >
+ <TextView
+ android:id="@+id/s0"
+ android:layout_width="250px"
+ android:layout_height="wrap_content"
+ android:text="@string/help_head"
+ android:textColor="#000000"
+ android:layout_marginLeft = "5px"
+ />
+ <TextView
+ android:id="@+id/s0"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background ="@drawable/icon"
+ />
+ </LinearLayout>
+ <TextView
+ android:id="@+id/s0"
+ android:layout_width="300px"
+ android:layout_height="5px"
+ android:background ="@drawable/line"
+ android:layout_marginLeft = "3px"
+ />
+ <TextView
+ android:id="@+id/s0"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/help_text"
+ android:textColor="#000000"
+ android:textSize="13sp"
+ android:layout_marginLeft = "5px"
+ android:layout_marginRight = "5px"
+ />
+ <TextView
+ android:id="@+id/s1"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/url_1"
+ android:textColor="#000000"
+ android:layout_marginLeft = "5px"
+ />
+ <TextView
+ android:id="@+id/S2"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/url_2"
+ android:textColor="#000000"
+ android:layout_marginLeft = "5px"
+ />
+ </LinearLayout>
+</ScrollView>
diff --git a/res/layout/main.xml b/res/layout/main.xml
new file mode 100644
index 0000000..70dceae
--- /dev/null
+++ b/res/layout/main.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background = "@drawable/background"
+ >
+ <TextView android:id="@+id/s0"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="PowerTutor 1.4"
+ android:gravity="center"
+ android:layout_weight="0.1"
+ android:textSize="16pt"
+ android:textColor="#FFFFFF"
+ />
+ <FrameLayout
+ android:layout_width="fill_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ />
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ >
+ <FrameLayout
+ android:layout_width="0px"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ />
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="0px"
+ android:layout_height="fill_parent"
+ android:layout_weight="3"
+ >
+ <Button android:id="@+id/servicestartbutton"
+ android:layout_weight="0.25"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textSize="8pt"
+ />
+ <Button android:id="@+id/appviewerbutton"
+ android:layout_weight="0.25"
+ android:text="Application Viewer"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textSize="8pt"
+ />
+ <Button android:id="@+id/sysviewerbutton"
+ android:layout_weight="0.25"
+ android:text="System Viewer"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textSize="8pt"
+ />
+ <Button android:id="@+id/helpbutton"
+ android:text = "Help"
+ android:layout_weight="0.25"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:textSize="8pt"
+ />
+ </LinearLayout>
+ <FrameLayout
+ android:layout_width="0px"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ />
+ </LinearLayout>
+ <FrameLayout
+ android:layout_width="fill_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"
+ />
+</LinearLayout>
+
diff --git a/res/layout/misc_item_layout.xml b/res/layout/misc_item_layout.xml
new file mode 100644
index 0000000..b9b6365
--- /dev/null
+++ b/res/layout/misc_item_layout.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:paddingRight="?android:attr/scrollbarSize">
+
+ <RelativeLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="15dip"
+ android:layout_marginRight="6dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_weight="1">
+
+ <TextView android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+
+ <TextView android:id="@+id/summary"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:layout_alignLeft="@id/title"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:maxLines="4" />
+
+ </RelativeLayout>
+
+ <LinearLayout android:id="@+id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="fill_parent"
+ android:gravity="center_vertical"
+ android:orientation="vertical" />
+
+</LinearLayout>
diff --git a/res/layout/misc_layout.xml b/res/layout/misc_layout.xml
new file mode 100644
index 0000000..676e495
--- /dev/null
+++ b/res/layout/misc_layout.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:paddingLeft="8dp"
+ android:paddingRight="8dp">
+ <ListView android:id="@id/android:list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="#00FF00"
+ android:layout_weight="1"
+ android:drawSelectorOnTop="false"/>
+ <TextView android:id="@id/android:empty"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:background="#FF0000"
+ android:text="No data"/>
+ </LinearLayout>
diff --git a/res/layout/power_tabs.xml b/res/layout/power_tabs.xml
new file mode 100644
index 0000000..f37bc75
--- /dev/null
+++ b/res/layout/power_tabs.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@android:id/tabhost"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+ <LinearLayout
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="5dp">
+ <TabWidget
+ android:id="@android:id/tabs"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+ <FrameLayout
+ android:id="@android:id/tabcontent"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:padding="5dp" />
+ </LinearLayout>
+</TabHost>
diff --git a/res/layout/widget_configure.xml b/res/layout/widget_configure.xml
new file mode 100644
index 0000000..61a0982
--- /dev/null
+++ b/res/layout/widget_configure.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:paddingLeft="8dp"
+ android:paddingRight="8dp">
+ <ListView android:id="@+id/list"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:drawSelectorOnTop="false"
+ android:layout_weight="0.15"/>
+ <LinearLayout
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="0.85">
+ <Button android:id="@+id/save_button"
+ android:text="Save"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:gravity="center"
+ />
+ <Button android:id="@+id/cancel_button"
+ android:text="Cancel"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1"
+ android:gravity="center"
+ />
+ </LinearLayout>
+</LinearLayout>
diff --git a/res/layout/widget_item_layout.xml b/res/layout/widget_item_layout.xml
new file mode 100644
index 0000000..c161604
--- /dev/null
+++ b/res/layout/widget_item_layout.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_marginLeft="15dip"
+ android:layout_marginRight="6dip"
+ android:layout_marginTop="6dip"
+ android:layout_marginBottom="6dip"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:gravity="center_vertical"
+ android:paddingRight="?android:attr/scrollbarSize">
+ <TextView android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal" />
+ <TextView android:id="@+id/summary"
+ android:gravity="center"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:layout_alignLeft="@id/title"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:maxLines="4" />
+</RelativeLayout>
diff --git a/res/layout/widget_layout.xml b/res/layout/widget_layout.xml
new file mode 100644
index 0000000..64e2c90
--- /dev/null
+++ b/res/layout/widget_layout.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:paddingLeft="14dp"
+ android:paddingRight="14dp"
+ android:paddingTop="17dp"
+ android:paddingBottom="17dp"
+ android:background="@drawable/widget_bg"
+ >
+ <ImageView android:id="@+id/power_button"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="0.25"
+ android:paddingRight="3dp"
+ android:src="@drawable/power_off"
+ android:clickable="true"
+ android:scaleType="centerInside"
+ />
+ <TextView android:id="@+id/text_minute"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="0.25"
+ android:paddingRight="3dp"
+ android:gravity="center"
+ />
+ <TextView android:id="@+id/text_hour"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="0.25"
+ android:paddingRight="3dp"
+ android:gravity="center"
+ />
+ <TextView android:id="@+id/text_day"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="0.25"
+ android:paddingRight="3dp"
+ android:gravity="center"
+ />
+</LinearLayout>
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..cf2b387
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,30 @@
+<resources>
+ <string-array name="xaxis">
+ <item>Last 10 seconds</item>
+ <item>Last 30 seconds</item>
+ <item>Last 1 minute</item>
+ <item>Last 2 minutes</item>
+ <item>Last 5 minutes</item>
+ </string-array>
+ <integer-array name="xaxis_secs">
+ <item>10</item>
+ <item>30</item>
+ <item>60</item>
+ <item>120</item>
+ <item>300</item>
+ </integer-array>
+ <string-array name="xaxis_secs_str">
+ <item>10</item>
+ <item>30</item>
+ <item>60</item>
+ <item>120</item>
+ <item>300</item>
+ </string-array>
+ <string-array name="freq">
+ <item>Every 1 second</item>
+ <item>Every 3 seconds</item>
+ <item>Every 6 seconds</item>
+ <item>Every 12 seconds</item>
+ <item>Every 30 seconds</item>
+ </string-array>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 0000000..fc667a6
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">PowerTutor</string>
+ <string name="running_on_startup">The Power Profiler will now run after your phone starts up.</string>
+ <string name="not_running_on_startup">The Power Profiler will no longer run on when your phone starts up.</string>
+ <string name="stop_sending_text">This application sends data on power utilization and running applications to the PowerTutor team. We gather nothing allowing us to associate the data with you or your phone; a cryptographically secure hash function is used to protect your identity. Pressing \'Stop\' will prevent these data from being sent.</string>
+ <string name="start_sending_text">This application sends data on power utilization and running applications to the PowerTutor team. We gather nothing allowing us to associate the data with you or your phone; a cryptographically secure hash function is used to protect your identity. Pressing \'Start\' will start sending the data from now on.</string>
+ <string name="unknown_phone">Your phone type is not recognized by PowerTutor. You may continue to use PowerTutor however we may not be able to get all of the data for your phone and the power calculations may not be accurate.</string>
+ <string name="term">
+ <p>In this version of PowerTutor the Power Profiler service is run on
+startup and power consumption traces are sent back to the PowerTutor team. This
+helps us identify common power consumption problems for real applications. The
+information will be used to improve PowerTutor and recommend power efficiency
+improvements to other software, with the end goal of increasing your phone\'s
+battery life.\n</p>\n
+ <p>We gather\n
+ - CPU use\n
+ - Amount of WiFi/3G traffic (not its content)\n
+ - Screen brightness\n
+ - GPS activity (but not location)\n
+ - Audio use (on/off, volume)\n
+ - Total power consumption\n
+ - Application resource utilization\n
+ - Phone mode, e.g., airplane mode or call status\n
+</p>
+ <p>\nWe do <b>NOT</b> gather any of your personal data used in applications
+or phone call logs. None of these data will be associate with you or your phone.
+We use a cryptographically secure hash function to protect your identity.\n
+</p>\n
+ <p>PowerTutor will send back traces only when the phone is plugged in and
+will prefer to use Wifi to send back the traces, although your carrier\'s data
+service will be used if Wifi is rarely available. If you would like to disable
+sending traces you can do so in the menu or by turning off background data in
+the android options.
+ </p></string>
+ <string name ="help_head"> <big><b>PowerTutor</b></big> - A real-time system and application power monitor.</string>
+ <string name ="help_text">\n
+ <ul>
+ <li><b>Main control panel</b>\n
+ - Start/stop profiling services. \n
+ - Select <b>Application</b> or <b>System</b> mode to check out power consumption statistics.\n
+ - Menu features:\n
+ \t* Start/stop sending power traces to the PowerTutor team.\n
+ \t* Enable/Disable the profiling service to run on startup.\n
+ \t* Save the current power trace to the sdcard.\n
+ </li>\n
+ <li><b>App View</b>\n
+ - Displays power usage information for all of the apps running on the
+ phone.\n
+ - Press an app row to go to the <i>Graph View</i> for that app\'s power
+ usage.\n
+ - Press a hardware component name in the top tabs to include/exclude it from power
+ calculations.\n
+ - Data interpretation:\n
+ \t* Percentage value: The percentage of energy/average power consumed by the
+ app in system.\n
+ \t* Time value: Time that the app has been actively monitored by PowerTutor.\n
+ - Menu features:\n
+ \t* Change the sort key to one of current power, average power, or total energy usage.\n
+ \t* Change the time span to include the last minute, hour, day, or all time.\n
+ </li>\n
+ <li><b>Graph View</b>\n
+ - Displays app/system power consumption decomposed by hardware component.\n
+ - Use the tabs at the top to move between <i>Chart View</i> and <i>Pie
+ View</i>.\n
+ </li>\n
+ <li><b>Chart View</b>\n
+ - Displays the power usage of different components on the phone in recent
+ history.\n
+ - Scroll to view different component power graphs.\n
+ - Menu features:\n
+ \t* Select \"<i>Set History Size</i>\" to adjust the number of data points
+ displayed.\n
+ \t* Select \"<i>Magnify Recent Data</i>\" to show more recent data points
+ more spread out.\n
+ \t* Select \"<i>Pause</i>\" to freeze the display as is, select resume to
+ continue.\n
+ </li>\n
+ <li><b>Pie View</b>\n
+ - Displays the power usage percentages of the different components.\n
+ - Menu features:\n
+ \t* Change the time span to include the last minute, hour, day, or all time.\n
+ </li>\n
+ </ul>
+ </string>
+
+
+<string name="url_1">For detailed information, please go to: </string>
+<string name="url_2"><a href = "http://powertutor.org">http://powertutor.org</a>.\n</string>
+</resources>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
new file mode 100644
index 0000000..a4bb226
--- /dev/null
+++ b/res/xml/preferences.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <CheckBoxPreference
+ android:key="runOnStartup"
+ android:title="Run on startup"
+ android:summary="Have the profiling service start when your phone starts" />
+ <CheckBoxPreference
+ android:key="sendPermission"
+ android:title="Send logs"
+ android:summary="Send back power traces to the PowerTutor team" />
+</PreferenceScreen>
diff --git a/res/xml/viewer_preferences.xml b/res/xml/viewer_preferences.xml
new file mode 100644
index 0000000..b34c5e7
--- /dev/null
+++ b/res/xml/viewer_preferences.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <CheckBoxPreference
+ android:key="showTotalPower"
+ android:title="Total power"
+ android:summary="Show total power consumption" />
+ <ListPreference android:key="viewNumValues_s"
+ android:title="Set history size"
+ android:summary="Change how many data points are displayed"
+ android:entries="@array/xaxis"
+ android:entryValues="@array/xaxis_secs_str"
+ android:dialogTitle="Set number of data points"
+ android:defaultValue="60" />
+</PreferenceScreen>
diff --git a/res/xml/widget_info.xml b/res/xml/widget_info.xml
new file mode 100644
index 0000000..df768b6
--- /dev/null
+++ b/res/xml/widget_info.xml
@@ -0,0 +1,8 @@
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:configure="edu.umich.PowerTutor.widget.Configure"
+ android:minWidth="294dp"
+ android:minHeight="72dp"
+ android:updatePeriodMillis="0"
+ android:initialLayout="@layout/widget_layout"
+ >
+</appwidget-provider>
diff --git a/src/edu/umich/PowerTutor/PowerNotifications.aidl b/src/edu/umich/PowerTutor/PowerNotifications.aidl
new file mode 100644
index 0000000..a94d83a
--- /dev/null
+++ b/src/edu/umich/PowerTutor/PowerNotifications.aidl
@@ -0,0 +1,70 @@
+/*
+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;
+
+interface PowerNotifications {
+ // These are the notifications that are actually supported. The rest have
+ // potential to be supported but aren't needed at the moment so aren't
+ // actually hooked.
+ void noteStartWakelock(int uid, String name, int type);
+ void noteStopWakelock(int uid, String name, int type);
+ void noteStartSensor(int uid, int sensor);
+ void noteStopSensor(int uid, int sensor);
+ void noteStartGps(int uid);
+ void noteStopGps(int uid);
+ void noteScreenBrightness(int brightness);
+ void noteStartMedia(int uid, int id);
+ void noteStopMedia(int uid, int id);
+ void noteVideoSize(int uid, int id, int width, int height);
+ void noteSystemMediaCall(int uid);
+
+ void noteScreenOn();
+ void noteScreenOff();
+ void noteInputEvent();
+ void noteUserActivity(int uid, int event);
+ void notePhoneOn();
+ void notePhoneOff();
+ void notePhoneDataConnectionState(int dataType, boolean hasData);
+ void noteWifiOn(int uid);
+ void noteWifiOff(int uid);
+ void noteWifiRunning();
+ void noteWifiStopped();
+ void noteBluetoothOn();
+ void noteBluetoothOff();
+ void noteFullWifiLockAcquired(int uid);
+ void noteFullWifiLockReleased(int uid);
+ void noteScanWifiLockAcquired(int uid);
+ void noteScanWifiLockReleased(int uid);
+ void noteWifiMulticastEnabled(int uid);
+ void noteWifiMulticastDisabled(int uid);
+ void setOnBattery(boolean onBattery, int level);
+ void recordCurrentLevel(int level);
+ /* Also got rid of the non-notification calls.
+ * byte[] getStatistics();
+ * long getAwakeTimeBattery();
+ * long getAwakeTimePlugged();
+ */
+
+ /* Added functions to the normal IBatteryStats interface. */
+ void noteVideoOn(int uid);
+ void noteVideoOff(int uid);
+ void noteAudioOn(int uid);
+ void noteAudioOff(int uid);
+}
diff --git a/src/edu/umich/PowerTutor/components/Audio.java b/src/edu/umich/PowerTutor/components/Audio.java
new file mode 100644
index 0000000..b370b83
--- /dev/null
+++ b/src/edu/umich/PowerTutor/components/Audio.java
@@ -0,0 +1,188 @@
+/*
+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.components;
+
+import edu.umich.PowerTutor.PowerNotifications;
+import edu.umich.PowerTutor.service.IterationData;
+import edu.umich.PowerTutor.service.PowerData;
+import edu.umich.PowerTutor.util.NotificationService;
+import edu.umich.PowerTutor.util.Recycler;
+
+import android.content.Context;
+import android.media.AudioManager;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.TreeSet;
+
+/**This class aims to log the audio device status once per log interval*/
+public class Audio extends PowerComponent {
+ /**This class is the logger data file corresponding to Audio*/
+ public static class AudioData extends PowerData {
+ private static Recycler<AudioData> recycler = new Recycler<AudioData>();
+
+ public static AudioData obtain() {
+ AudioData result = recycler.obtain();
+ if(result != null) return result;
+ return new AudioData();
+ }
+
+ @Override
+ public void recycle() {
+ recycler.recycle(this);
+ }
+
+ public boolean musicOn;
+
+ private AudioData() {
+ }
+
+ public void init(boolean musicOn) {
+ this.musicOn = musicOn;
+ }
+
+ public void writeLogDataInfo(OutputStreamWriter out) throws IOException {
+ out.write("Audio-on " + musicOn + "\n");
+ }
+ }
+
+ private static class MediaData implements Comparable {
+ private static Recycler<MediaData> recycler = new Recycler<MediaData>();
+
+ public static MediaData obtain() {
+ MediaData result = recycler.obtain();
+ if(result != null) return result;
+ return new MediaData();
+ }
+
+ public void recycle() {
+ recycler.recycle(this);
+ }
+
+ public int uid;
+ public int id;
+ public int assignUid;
+
+ public int compareTo(Object obj) {
+ MediaData x = (MediaData)obj;
+ if(uid < x.uid) return -1;
+ if(uid > x.uid) return 1;
+ if(id < x.id) return -1;
+ if(id > x.id) return 1;
+ return 0;
+ }
+
+ public boolean equals(Object obj) {
+ MediaData x = (MediaData)obj;
+ return uid == x.uid && id == x.id;
+ }
+ }
+
+ private AudioManager audioManager;
+ private PowerNotifications audioNotif;
+ private TreeSet<MediaData> uidData;
+
+ public Audio(Context context) {
+ if(NotificationService.available()) {
+ uidData = new TreeSet<MediaData>();
+ audioNotif = new NotificationService.DefaultReceiver() {
+ private int sysUid = -1;
+
+ @Override
+ public void noteSystemMediaCall(int uid) {
+ sysUid = uid;
+ }
+
+ @Override
+ public void noteStartMedia(int uid, int id) {
+ MediaData data = MediaData.obtain();
+ data.uid = uid;
+ data.id = id;
+ if(uid == 1000 && sysUid != -1) {
+ data.assignUid = sysUid;
+ sysUid = -1;
+ } else {
+ data.assignUid = uid;
+ }
+ synchronized(uidData) {
+ if(!uidData.add(data)) {
+ data.recycle();
+ }
+ }
+ }
+
+ @Override
+ public void noteStopMedia(int uid, int id) {
+ MediaData data = MediaData.obtain();
+ data.uid = uid;
+ data.id = id;
+ synchronized(uidData) {
+ uidData.remove(data);
+ }
+ data.recycle();
+ }
+ };
+ NotificationService.addHook(audioNotif);
+ }
+
+ audioManager = (AudioManager)context.getSystemService(
+ Context.AUDIO_SERVICE);
+ }
+
+ @Override
+ protected void onExit() {
+ if(audioNotif != null) {
+ NotificationService.removeHook(audioNotif);
+ }
+ }
+
+ @Override
+ public IterationData calculateIteration(long iteration) {
+ IterationData result = IterationData.obtain();
+ AudioData data = AudioData.obtain();
+ data.init(uidData != null && !uidData.isEmpty() ||
+ audioManager.isMusicActive());
+ result.setPowerData(data);
+
+ if(uidData != null) synchronized(uidData) {
+ int last_uid = -1;
+ for(MediaData dat : uidData) {
+ if(dat.uid != last_uid) {
+ AudioData audioPower = AudioData.obtain();
+ audioPower.init(true);
+ result.addUidPowerData(dat.assignUid, audioPower);
+ }
+ last_uid = dat.uid;
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean hasUidInformation() {
+ return audioNotif != null;
+ }
+
+ @Override
+ public String getComponentName() {
+ return "Audio";
+ }
+}
diff --git a/src/edu/umich/PowerTutor/components/CPU.java b/src/edu/umich/PowerTutor/components/CPU.java
new file mode 100644
index 0000000..283e991
--- /dev/null
+++ b/src/edu/umich/PowerTutor/components/CPU.java
@@ -0,0 +1,358 @@
+/*
+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.components;
+
+import edu.umich.PowerTutor.phone.PhoneConstants;
+import edu.umich.PowerTutor.service.IterationData;
+import edu.umich.PowerTutor.service.PowerData;
+import edu.umich.PowerTutor.util.Recycler;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+import android.util.Log;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.SparseArray;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+public class CPU extends PowerComponent {
+ public static class CpuData extends PowerData {
+ private static Recycler<CpuData> recycler = new Recycler<CpuData>();
+
+ public static CpuData obtain() {
+ CpuData result = recycler.obtain();
+ if(result != null) return result;
+ return new CpuData();
+ }
+
+ @Override
+ public void recycle() {
+ recycler.recycle(this);
+ }
+
+ public double sysPerc;
+ public double usrPerc;
+ public double freq;
+
+ private CpuData() {
+ }
+
+ public void init(double sysPerc, double usrPerc, double freq) {
+ this.sysPerc = sysPerc;
+ this.usrPerc = usrPerc;
+ this.freq = freq;
+ }
+
+ public void writeLogDataInfo(OutputStreamWriter out) throws IOException {
+ StringBuilder res = new StringBuilder();
+ res.append("CPU-sys ").append((long)Math.round(sysPerc))
+ .append("\nCPU-usr ").append((long)Math.round(usrPerc))
+ .append("\nCPU-freq ").append(freq)
+ .append("\n");
+ out.write(res.toString());
+ }
+ }
+
+ private static final String TAG = "CPU";
+ private static final String CPU_FREQ_FILE = "/proc/cpuinfo";
+ private static final String STAT_FILE = "/proc/stat";
+
+ private CpuStateKeeper cpuState;
+ private SparseArray<CpuStateKeeper> pidStates;
+ private SparseArray<CpuStateKeeper> uidLinks;
+
+ private int[] pids;
+ private long[] statsBuf;
+
+ private PhoneConstants constants;
+
+ public CPU(PhoneConstants constants) {
+ this.constants = constants;
+ cpuState = new CpuStateKeeper(SystemInfo.AID_ALL);
+ pidStates = new SparseArray<CpuStateKeeper>();
+ uidLinks = new SparseArray<CpuStateKeeper>();
+ statsBuf = new long[7];
+ }
+
+ @Override
+ public IterationData calculateIteration(long iteration) {
+ IterationData result = IterationData.obtain();
+
+ SystemInfo sysInfo = SystemInfo.getInstance();
+ double freq = readCpuFreq(sysInfo);
+ if(freq < 0) {
+ Log.w(TAG, "Failed to read cpu frequency");
+ return result;
+ }
+
+ if(!sysInfo.getUsrSysTotalTime(statsBuf)) {
+ Log.w(TAG, "Failed to read cpu times");
+ return result;
+ }
+
+ long usrTime = statsBuf[SystemInfo.INDEX_USER_TIME];
+ long sysTime = statsBuf[SystemInfo.INDEX_SYS_TIME];
+ long totalTime = statsBuf[SystemInfo.INDEX_TOTAL_TIME];
+
+ boolean init = cpuState.isInitialized();
+ cpuState.updateState(usrTime, sysTime, totalTime, iteration);
+
+ if(init) {
+ CpuData data = CpuData.obtain();
+ data.init(cpuState.getUsrPerc(), cpuState.getSysPerc(), freq);
+ result.setPowerData(data);
+ }
+
+ uidLinks.clear();
+ pids = sysInfo.getPids(pids);
+ int pidInd = 0;
+ if(pids != null) for(int pid : pids) {
+ if(pid < 0) {
+ break;
+ }
+
+ CpuStateKeeper pidState;
+ if(pidInd < pidStates.size() && pidStates.keyAt(pidInd) == pid) {
+ pidState = pidStates.valueAt(pidInd);
+ } else {
+ int uid = sysInfo.getUidForPid(pid);
+ if(uid >= 0) {
+ pidState = new CpuStateKeeper(uid);
+ pidStates.put(pid, pidState);
+ } else {
+ /* Assume that this process no longer exists. */
+ continue;
+ }
+ }
+ pidInd++;
+
+ if(!pidState.isStale(iteration)) {
+ /* Nothing much is going on with this pid recently. We'll just
+ * assume that it's not using any of the cpu for this iteration.
+ */
+ pidState.updateIteration(iteration, totalTime);
+ } else if(sysInfo.getPidUsrSysTime(pid, statsBuf)) {
+ usrTime = statsBuf[SystemInfo.INDEX_USER_TIME];
+ sysTime = statsBuf[SystemInfo.INDEX_SYS_TIME];
+
+ init = pidState.isInitialized();
+ pidState.updateState(usrTime, sysTime, totalTime, iteration);
+
+ if(!init) {
+ continue;
+ }
+ }
+
+ CpuStateKeeper linkState = uidLinks.get(pidState.getUid());
+ if(linkState == null) {
+ uidLinks.put(pidState.getUid(), pidState);
+ } else {
+ linkState.absorb(pidState);
+ }
+ }
+
+ /* Remove processes that are no longer active. */
+ for(int i = 0; i < pidStates.size(); i++) {
+ if(!pidStates.valueAt(i).isAlive(iteration)) {
+ pidStates.remove(pidStates.keyAt(i--));
+ }
+ }
+
+ /* Collect the summed uid information. */
+ for(int i = 0; i < uidLinks.size(); i++) {
+ int uid = uidLinks.keyAt(i);
+ CpuStateKeeper linkState = uidLinks.valueAt(i);
+
+ CpuData uidData = CpuData.obtain();
+ predictAppUidState(uidData, linkState.getUsrPerc(),
+ linkState.getSysPerc(), freq);
+ result.addUidPowerData(uid, uidData);
+ }
+
+ return result;
+ }
+
+ /* This is the function that is responsible for predicting the cpu frequency
+ * state of the individual uid as though it were the only thing running. It
+ * simply is finding the lowest frequency that keeps the cpu usage under
+ * 70% assuming there is a linear relationship to the cpu utilization at
+ * different frequencies.
+ */
+ private void predictAppUidState(CpuData uidData, double usrPerc,
+ double sysPerc, double freq) {
+ double[] freqs = constants.cpuFreqs();
+ if(usrPerc + sysPerc < 1e-6) {
+ /* Don't waste time with the binary search if there is no utilization
+ * which will be the case a lot.
+ */
+ uidData.init(sysPerc, usrPerc, freqs[0]);
+ return;
+ }
+ int lo = 0;
+ int hi = freqs.length - 1;
+ double perc = sysPerc + usrPerc;
+ while(lo < hi) {
+ int mid = (lo + hi) / 2;
+ double nperc = perc * freq / freqs[mid];
+ if(nperc < 70) {
+ hi = mid;
+ } else {
+ lo = mid + 1;
+ }
+ }
+ uidData.init(sysPerc * freq / freqs[lo], usrPerc * freq / freqs[lo],
+ freqs[lo]);
+ }
+
+ private static class CpuStateKeeper {
+ private int uid;
+ private long iteration;
+ private long lastUpdateIteration;
+ private long inactiveIterations;
+
+ private long lastUsr;
+ private long lastSys;
+ private long lastTotal;
+
+ private long sumUsr;
+ private long sumSys;
+ private long deltaTotal;
+
+ private CpuStateKeeper(int uid) {
+ this.uid = uid;
+ lastUsr = lastSys = -1;
+ lastUpdateIteration = iteration = -1;
+ inactiveIterations = 0;
+ }
+
+ public boolean isInitialized() {
+ return lastUsr != -1;
+ }
+
+ public void updateIteration(long iteration, long totalTime) {
+ /* Process is still running but actually reading the cpu utilization has
+ * been skipped this iteration to avoid wasting cpu cycles as this process
+ * has not been very active recently. */
+ sumUsr = 0;
+ sumSys = 0;
+ deltaTotal = totalTime - lastTotal;
+ if(deltaTotal < 1) deltaTotal = 1;
+ lastTotal = totalTime;
+ this.iteration = iteration;
+ }
+
+ public void updateState(long usrTime, long sysTime, long totalTime,
+ long iteration) {
+ sumUsr = usrTime - lastUsr;
+ sumSys = sysTime - lastSys;
+ deltaTotal = totalTime - lastTotal;
+ if(deltaTotal < 1) deltaTotal = 1;
+ lastUsr = usrTime;
+ lastSys = sysTime;
+ lastTotal = totalTime;
+ lastUpdateIteration = this.iteration = iteration;
+
+ if(getUsrPerc() + getSysPerc() < 0.1) {
+ inactiveIterations++;
+ } else {
+ inactiveIterations = 0;
+ }
+ }
+
+ public int getUid() {
+ return uid;
+ }
+
+ public void absorb(CpuStateKeeper s) {
+ sumUsr += s.sumUsr;
+ sumSys += s.sumSys;
+ }
+
+ public double getUsrPerc() {
+ return 100.0 * sumUsr / Math.max(sumUsr + sumSys, deltaTotal);
+ }
+
+ public double getSysPerc() {
+ return 100.0 * sumSys / Math.max(sumUsr + sumSys, deltaTotal);
+ }
+
+ public boolean isAlive(long iteration) {
+ return this.iteration == iteration;
+ }
+
+ public boolean isStale(long iteration) {
+ return 1L << (iteration - lastUpdateIteration) >
+ inactiveIterations * inactiveIterations;
+ }
+ }
+
+ @Override
+ public boolean hasUidInformation() {
+ return true;
+ }
+
+ @Override
+ public String getComponentName() {
+ return "CPU";
+ }
+
+ /* Returns the frequency of the processor in Mhz. If the frequency cannot
+ * be determined returns a negative value instead.
+ */
+ private double readCpuFreq(SystemInfo sysInfo) {
+ /* Try to read from the /sys/devices file first. If that doesn't work
+ * try manually inspecting the /proc/cpuinfo file.
+ */
+ long cpuFreqKhz = sysInfo.readLongFromFile(
+ "/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq");
+ if(cpuFreqKhz != -1) {
+ return cpuFreqKhz / 1000.0;
+ }
+
+ FileReader fstream;
+ try {
+ fstream = new FileReader(CPU_FREQ_FILE);
+ } catch (FileNotFoundException e) {
+ Log.w(TAG, "Could not read cpu frequency file");
+ return -1;
+ }
+ BufferedReader in = new BufferedReader(fstream, 500);
+ String line;
+ try {
+ while((line = in.readLine()) != null) {
+ if(line.startsWith("BogoMIPS")) {
+ return Double.parseDouble(line.trim().split("[ :]+")[1]);
+ }
+ }
+ } catch(IOException e) {
+ /* Failed to read from the cpu freq file. */
+ } catch(NumberFormatException e) {
+ /* Frequency not formatted properly as a double. */
+ }
+ Log.w(TAG, "Failed to read cpu frequency");
+ return -1;
+ }
+}
diff --git a/src/edu/umich/PowerTutor/components/GPS.java b/src/edu/umich/PowerTutor/components/GPS.java
new file mode 100644
index 0000000..c350819
--- /dev/null
+++ b/src/edu/umich/PowerTutor/components/GPS.java
@@ -0,0 +1,456 @@
+/*
+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.components;
+
+import edu.umich.PowerTutor.PowerNotifications;
+import edu.umich.PowerTutor.phone.PhoneConstants;
+import edu.umich.PowerTutor.service.IterationData;
+import edu.umich.PowerTutor.service.PowerData;
+import edu.umich.PowerTutor.util.NotificationService;
+import edu.umich.PowerTutor.util.Recycler;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+import android.content.Context;
+import android.location.GpsSatellite;
+import android.location.GpsStatus;
+import android.location.LocationManager;
+import android.os.Build;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Map;
+
+public class GPS extends PowerComponent {
+ public static class GpsData extends PowerData {
+ private static Recycler<GpsData> recycler = new Recycler<GpsData>();
+
+ public static GpsData obtain() {
+ GpsData result = recycler.obtain();
+ if(result != null) return result;
+ return new GpsData();
+ }
+
+ /* The time in seconds since the last iteration of data. */
+ public double[] stateTimes;
+ /* The number of satellites. This number is only available while the GPS is
+ * in the on state. Otherwise it is 0.
+ */
+ public int satellites;
+
+ private GpsData() {
+ stateTimes = new double[GPS.POWER_STATES];
+ }
+
+ public void init(double[] stateTimes, int satellites) {
+ for(int i = 0; i < GPS.POWER_STATES; i++) {
+ this.stateTimes[i] = stateTimes[i];
+ }
+ this.satellites = satellites;
+ }
+
+ @Override
+ public void recycle() {
+ recycler.recycle(this);
+ }
+
+ @Override
+ public void writeLogDataInfo(OutputStreamWriter out) throws IOException {
+ StringBuilder res = new StringBuilder();
+ res.append("GPS-state-times");
+ for(int i = 0; i < GPS.POWER_STATES; i++) {
+ res.append(" ").append(stateTimes[i]);
+ }
+ res.append("\nGPS-sattelites ").append(satellites).append("\n");
+ out.write(res.toString());
+ }
+ }
+
+ public static final int POWER_STATES = 3;
+ public static final int POWER_STATE_OFF = 0;
+ public static final int POWER_STATE_SLEEP = 1;
+ public static final int POWER_STATE_ON = 2;
+ public static final String[] POWER_STATE_NAMES = {"OFF", "SLEEP", "ON"};
+
+ private static final String TAG = "GPS";
+
+ private static final int HOOK_LIBGPS = 1;
+ private static final int HOOK_GPS_STATUS_LISTENER = 2;
+ private static final int HOOK_NOTIFICATIONS = 4;
+ private static final int HOOK_TIMER = 8;
+
+ /* A named pipe written to by the hacked libgps library. */
+ private static String HOOK_GPS_STATUS_FILE = "/data/misc/gps.status";
+
+ private GpsStatus.Listener gpsListener;
+ private Thread statusThread;
+ private PowerNotifications notificationReceiver;
+
+ private Context context;
+ private LocationManager locationManager;
+ private GpsStatus lastStatus;
+ private boolean hasUidInfo;
+ private long sleepTime;
+ private long lastTime;
+
+ private GpsStateKeeper gpsState;
+ private SparseArray<GpsStateKeeper> uidStates;
+
+ private static final int GPS_STATUS_SESSION_BEGIN = 1;
+ private static final int GPS_STATUS_SESSION_END = 2;
+ private static final int GPS_STATUS_ENGINE_ON = 3;
+ private static final int GPS_STATUS_ENGINE_OFF = 4;
+
+ public GPS(Context context, PhoneConstants constants) {
+ this.context = context;
+ uidStates = new SparseArray<GpsStateKeeper>();
+ sleepTime = (long)Math.round(1000.0 * constants.gpsSleepTime());
+
+ hasUidInfo = NotificationService.available();
+
+ int hookMethod = 0;
+ final File gpsStatusFile = new File(HOOK_GPS_STATUS_FILE);
+ if(gpsStatusFile.exists()) {
+ /* The libgps hack appears to be available. Let's use this to gather
+ * our status updates from the GPS.
+ */
+ hookMethod = HOOK_LIBGPS;
+ } else {
+ /* We can always use the status listener hook and perhaps the notification
+ * hook if we are running eclaire or higher and the notification hook
+ * is installed. We can only do this on eclaire or higher because it
+ * wasn't until eclaire that they fixed a bug where they didn't maintain
+ * a wakelock while the gps engine was on.
+ */
+ hookMethod = HOOK_GPS_STATUS_LISTENER;
+ try {
+ if(NotificationService.available() &&
+ Integer.parseInt(Build.VERSION.SDK) >= 5 /* eclaire or higher */) {
+ hookMethod |= HOOK_NOTIFICATIONS;
+ }
+ } catch(NumberFormatException e) {
+ Log.w(TAG, "Could not parse sdk version: " + Build.VERSION.SDK);
+ }
+ }
+ /* If we don't have a way of getting the off<->sleep transitions through
+ * notifications let's just use a timer and simulat the state of the gps
+ * instead.
+ */
+ if((hookMethod & (HOOK_LIBGPS | HOOK_NOTIFICATIONS)) == 0) {
+ hookMethod |= HOOK_TIMER;
+ }
+
+ /* Create the object that keeps track of the physical GPS state. */
+ gpsState = new GpsStateKeeper(hookMethod, sleepTime);
+
+ /* No matter what we are going to register a GpsStatus listener so that we
+ * can get the satellite count. Also if anything goes wrong with the
+ * libgps hook we will revert to using this.
+ */
+ locationManager = (LocationManager)
+ context.getSystemService(Context.LOCATION_SERVICE);
+ gpsListener = new GpsStatus.Listener() {
+ public void onGpsStatusChanged(int event){
+ if(event == GpsStatus.GPS_EVENT_STARTED) {
+ gpsState.updateEvent(GPS_STATUS_SESSION_BEGIN,
+ HOOK_GPS_STATUS_LISTENER);
+ } else if(event == GpsStatus.GPS_EVENT_STOPPED) {
+ gpsState.updateEvent(GPS_STATUS_SESSION_END,
+ HOOK_GPS_STATUS_LISTENER);
+ }
+ synchronized(GPS.this) {
+ lastStatus = locationManager.getGpsStatus(lastStatus);
+ }
+ }
+ };
+ locationManager.addGpsStatusListener(gpsListener);
+
+ /* No matter what we register a notification service listener as well so
+ * that we can get uid information if it's available.
+ */
+ if(hasUidInfo) {
+ notificationReceiver = new NotificationService.DefaultReceiver() {
+ public void noteStartWakelock(int uid, String name, int type) {
+ if(uid == SystemInfo.AID_SYSTEM &&
+ "GpsLocationProvider".equals(name)) {
+ gpsState.updateEvent(GPS_STATUS_ENGINE_ON, HOOK_NOTIFICATIONS);
+ }
+ }
+
+ public void noteStopWakelock(int uid, String name, int type) {
+ if(uid == SystemInfo.AID_SYSTEM &&
+ "GpsLocationProvider".equals(name)) {
+ gpsState.updateEvent(GPS_STATUS_ENGINE_OFF, HOOK_NOTIFICATIONS);
+ }
+ }
+
+ public void noteStartGps(int uid) {
+ updateUidEvent(uid, GPS_STATUS_SESSION_BEGIN, HOOK_NOTIFICATIONS);
+ }
+
+ public void noteStopGps(int uid) {
+ updateUidEvent(uid, GPS_STATUS_SESSION_END, HOOK_NOTIFICATIONS);
+ }
+ };
+ NotificationService.addHook(notificationReceiver);
+ }
+
+ if(gpsStatusFile.exists()) {
+ /* Start a thread to read from the named pipe and feed us status updates.
+ */
+ statusThread = new Thread() {
+ public void run() {
+ try {
+ java.io.FileInputStream fin =
+ new java.io.FileInputStream(gpsStatusFile);
+ for(int event = fin.read(); !interrupted() && event != -1;
+ event = fin.read()) {
+ gpsState.updateEvent(event, HOOK_LIBGPS);
+ }
+ } catch(IOException e) {
+ e.printStackTrace();
+ }
+ if(!interrupted()) {
+ // TODO: Have this instead just switch to use different hooks.
+ Log.w(TAG, "GPS status thread exited. " +
+ "No longer gathering gps data.");
+ }
+ }
+ };
+ statusThread.start();
+ }
+ }
+
+ private void updateUidEvent(int uid, int event, int source) {
+ synchronized(uidStates) {
+ GpsStateKeeper state = uidStates.get(uid);
+ if(state == null) {
+ state = new GpsStateKeeper(HOOK_NOTIFICATIONS | HOOK_TIMER, sleepTime,
+ lastTime);
+ uidStates.put(uid, state);
+ }
+ state.updateEvent(event, source);
+ }
+ }
+
+ @Override
+ protected void onExit() {
+ if(gpsListener != null) {
+ locationManager.removeGpsStatusListener(gpsListener);
+ }
+ if(statusThread != null) {
+ statusThread.interrupt();
+ }
+ if(notificationReceiver != null) {
+ NotificationService.removeHook(notificationReceiver);
+ }
+ super.onExit();
+ }
+
+ @Override
+ public IterationData calculateIteration(long iteration) {
+ IterationData result = IterationData.obtain();
+
+ /* Get the number of satellites that were available in the last update. */
+ int satellites = 0;
+ synchronized(this) {
+ if(lastStatus != null) {
+ for(GpsSatellite satellite : lastStatus.getSatellites()) {
+ satellites++;
+ }
+ }
+ }
+
+ /* Get the power data for the physical gps device. */
+ GpsData power = GpsData.obtain();
+ synchronized(gpsState) {
+ double[] stateTimes = gpsState.getStateTimesLocked();
+ int curState = gpsState.getCurrentStateLocked();
+ power.init(stateTimes, curState == POWER_STATE_ON ? satellites : 0);
+ gpsState.resetTimesLocked();
+ }
+ result.setPowerData(power);
+
+ /* Get the power data for each uid if we have information on it. */
+ if(hasUidInfo) synchronized(uidStates) {
+ lastTime = beginTime + iterationInterval * iteration;
+ for(int i = 0; i < uidStates.size(); i++) {
+ int uid = uidStates.keyAt(i);
+ GpsStateKeeper state = uidStates.valueAt(i);
+
+ double[] stateTimes = state.getStateTimesLocked();
+ int curState = state.getCurrentStateLocked();
+ GpsData uidPower = GpsData.obtain();
+ uidPower.init(stateTimes, curState == POWER_STATE_ON ? satellites : 0);
+ state.resetTimesLocked();
+
+ result.addUidPowerData(uid, uidPower);
+
+ /* Remove state information for uids no longer using the gps. */
+ if(curState == POWER_STATE_OFF) {
+ uidStates.remove(uid);
+ i--;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean hasUidInformation() {
+ return hasUidInfo;
+ }
+
+ /* This class is used to maintain the actual GPS state in addition to
+ * simulating individual uid states.
+ */
+ private static class GpsStateKeeper {
+ private double[] stateTimes;
+ private long lastTime;
+ private int curState;
+
+ /* The sum of whatever hook sources are valid. See the HOOK_ constants. */
+ private int hookMask;
+ /* The time that the GPS hardware should turn off. This is only used
+ * if HOOK_TIMER is in the hookMask.
+ */
+ private long offTime;
+ /* Gives the time that the GPS stays in the sleep state after the session
+ * has ended in milliseconds.
+ */
+ private long sleepTime;
+
+ public GpsStateKeeper(int hookMask, long sleepTime) {
+ this(hookMask, sleepTime, SystemClock.elapsedRealtime());
+ }
+
+ public GpsStateKeeper(int hookMask, long sleepTime, long lastTime) {
+ this.hookMask = hookMask;
+ this.sleepTime = sleepTime; /* This isn't required if HOOK_TIEMR is not
+ * set. */
+ this.lastTime = lastTime;
+ stateTimes = new double[POWER_STATES];
+ curState = POWER_STATE_OFF;
+ offTime = -1;
+ }
+
+ /* Make sure that you have a lock on this before calling. */
+ public double[] getStateTimesLocked() {
+ updateTimesLocked();
+
+ /* Let's normalize the times so that power measurements are consistent. */
+ double total = 0;
+ for(int i = 0; i < POWER_STATES; i++) {
+ total += stateTimes[i];
+ }
+ if(total == 0) total = 1;
+ for(int i = 0; i < POWER_STATES; i++) {
+ stateTimes[i] /= total;
+ }
+
+ return stateTimes;
+ }
+
+ public void resetTimesLocked() {
+ for(int i = 0; i < POWER_STATES; i++) {
+ stateTimes[i] = 0;
+ }
+ }
+
+ public int getCurrentStateLocked() {
+ return curState;
+ }
+
+ /* Make sure that you have a lock on this before calling. */
+ private void updateTimesLocked() {
+ /* Update the time we were in the previous state. */
+ long curTime = SystemClock.elapsedRealtime();
+
+ /* Check if the GPS has gone to sleep as a result of a timer. */
+ if((hookMask & HOOK_TIMER) != 0 && offTime != -1 &&
+ offTime < curTime) {
+ stateTimes[curState] += (offTime - lastTime) / 1000.0;
+ curState = POWER_STATE_OFF;
+ offTime = -1;
+ }
+
+ /* Update the amount of time that we've been in the current state. */
+ stateTimes[curState] += (curTime - lastTime) / 1000.0;
+ lastTime = curTime;
+ }
+
+ /* When a hook source gets an event it should report it to updateEvent.
+ * The only exception is HOOK_TIMER which is handled within this class
+ * itself.
+ */
+ public void updateEvent(int event, int source) {
+ synchronized(this) {
+ if((hookMask & source) == 0) {
+ /* We are not using this hook source, ignore. */
+ return;
+ }
+
+ updateTimesLocked();
+ int oldState = curState;
+ switch(event) {
+ case GPS_STATUS_SESSION_BEGIN:
+ curState = POWER_STATE_ON;
+ break;
+ case GPS_STATUS_SESSION_END:
+ if(curState == POWER_STATE_ON) {
+ curState = POWER_STATE_SLEEP;
+ }
+ break;
+ case GPS_STATUS_ENGINE_ON:
+ if(curState == POWER_STATE_OFF) {
+ curState = POWER_STATE_SLEEP;
+ }
+ break;
+ case GPS_STATUS_ENGINE_OFF:
+ curState = POWER_STATE_OFF;
+ break;
+ default:
+ Log.w(TAG, "Unknown GPS event captured");
+ }
+ if(curState != oldState) {
+ if(oldState == POWER_STATE_ON && curState == POWER_STATE_SLEEP) {
+ offTime = SystemClock.elapsedRealtime() + sleepTime;
+ } else {
+ /* Any other state transition should reset the off timer. */
+ offTime = -1;
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public String getComponentName() {
+ return "GPS";
+ }
+}
+
diff --git a/src/edu/umich/PowerTutor/components/LCD.java b/src/edu/umich/PowerTutor/components/LCD.java
new file mode 100644
index 0000000..869cd59
--- /dev/null
+++ b/src/edu/umich/PowerTutor/components/LCD.java
@@ -0,0 +1,179 @@
+/*
+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.components;
+
+import edu.umich.PowerTutor.PowerNotifications;
+import edu.umich.PowerTutor.service.IterationData;
+import edu.umich.PowerTutor.service.PowerData;
+import edu.umich.PowerTutor.util.ForegroundDetector;
+import edu.umich.PowerTutor.util.NotificationService;
+import edu.umich.PowerTutor.util.Recycler;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.Settings;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.List;
+
+public class LCD extends PowerComponent {
+ public static class LcdData extends PowerData {
+ private static Recycler<LcdData> recycler = new Recycler<LcdData>();
+
+ public static LcdData obtain() {
+ LcdData result = recycler.obtain();
+ if(result != null) return result;
+ return new LcdData();
+ }
+
+ @Override
+ public void recycle() {
+ recycler.recycle(this);
+ }
+
+ public int brightness;
+ public boolean screenOn;
+
+ private LcdData() {
+ }
+
+ public void init(int brightness, boolean screenOn) {
+ this.brightness = brightness;
+ this.screenOn = screenOn;
+ }
+
+ public void writeLogDataInfo(OutputStreamWriter out) throws IOException {
+ StringBuilder res = new StringBuilder();
+ res.append("LCD-brightness ").append(brightness)
+ .append("\nLCD-screen-on ").append(screenOn).append("\n");
+ out.write(res.toString());
+ }
+ }
+
+ private final String TAG = "LCD";
+ private static final String[] BACKLIGHT_BRIGHTNESS_FILES = {
+ "/sys/devices/virtual/leds/lcd-backlight/brightness",
+ "/sys/devices/platform/trout-backlight.0/leds/lcd-backlight/brightness",
+ };
+
+ private Context context;
+ private ForegroundDetector foregroundDetector;
+ private BroadcastReceiver broadcastReceiver;
+ private boolean screenOn;
+
+ private String brightnessFile;
+
+ public LCD(Context context) {
+ this.context = context;
+ screenOn = true;
+
+ if(context == null) {
+ return;
+ }
+
+ foregroundDetector = new ForegroundDetector((ActivityManager)
+ context.getSystemService(context.ACTIVITY_SERVICE));
+ broadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ synchronized(this) {
+ if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ screenOn = false;
+ } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
+ screenOn = true;
+ }
+ }
+ };
+ };
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ context.registerReceiver(broadcastReceiver, intentFilter);
+
+ for(int i = 0; i < BACKLIGHT_BRIGHTNESS_FILES.length; i++) {
+ if(new File(BACKLIGHT_BRIGHTNESS_FILES[i]).exists()) {
+ brightnessFile = BACKLIGHT_BRIGHTNESS_FILES[i];
+ }
+ }
+ }
+
+ @Override
+ protected void onExit() {
+ context.unregisterReceiver(broadcastReceiver);
+ super.onExit();
+ }
+
+ @Override
+ public IterationData calculateIteration(long iteration) {
+ IterationData result = IterationData.obtain();
+
+ boolean screen;
+ synchronized(this) {
+ screen = screenOn;
+ }
+
+ int brightness;
+ if(brightnessFile != null) {
+ brightness = (int)SystemInfo.getInstance()
+ .readLongFromFile(brightnessFile);
+ } else {
+ try {
+ brightness = Settings.System.getInt(context.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS);
+ } catch(Settings.SettingNotFoundException ex) {
+ Log.w(TAG, "Could not retrieve brightness information");
+ return result;
+ }
+ }
+ if(brightness < 0 || 255 < brightness) {
+ Log.w(TAG, "Could not retrieve brightness information");
+ return result;
+ }
+
+ LcdData data = LcdData.obtain();
+ data.init(brightness, screen);
+ result.setPowerData(data);
+
+ if(screen) {
+ LcdData uidData = LcdData.obtain();
+ uidData.init(brightness, screen);
+ result.addUidPowerData(foregroundDetector.getForegroundUid(), uidData);
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean hasUidInformation() {
+ return true;
+ }
+
+ @Override
+ public String getComponentName() {
+ return "LCD";
+ }
+}
diff --git a/src/edu/umich/PowerTutor/components/OLED.java b/src/edu/umich/PowerTutor/components/OLED.java
new file mode 100644
index 0000000..9987709
--- /dev/null
+++ b/src/edu/umich/PowerTutor/components/OLED.java
@@ -0,0 +1,311 @@
+/*
+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.components;
+
+import edu.umich.PowerTutor.PowerNotifications;
+import edu.umich.PowerTutor.phone.PhoneConstants;
+import edu.umich.PowerTutor.service.IterationData;
+import edu.umich.PowerTutor.service.PowerData;
+import edu.umich.PowerTutor.util.NativeLoader;
+import edu.umich.PowerTutor.util.NotificationService;
+import edu.umich.PowerTutor.util.Recycler;
+import edu.umich.PowerTutor.util.SystemInfo;
+import edu.umich.PowerTutor.util.ForegroundDetector;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.provider.Settings;
+import android.os.Process;
+import android.util.Log;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.RandomAccessFile;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.List;
+import java.util.Random;
+
+import java.io.*;
+import java.nio.*;
+import java.nio.channels.*;
+
+public class OLED extends PowerComponent {
+ public static class OledData extends PowerData {
+ private static Recycler<OledData> recycler = new Recycler<OledData>();
+
+ public static OledData obtain() {
+ OledData result = recycler.obtain();
+ if(result != null) return result;
+ return new OledData();
+ }
+
+ @Override
+ public void recycle() {
+ recycler.recycle(this);
+ }
+
+ public int brightness;
+ public double pixPower;
+ public boolean screenOn;
+
+ private OledData() {
+ }
+
+ public void init() {
+ this.screenOn = false;
+ }
+
+ public void init(int brightness, double pixPower) {
+ screenOn = true;
+ this.brightness = brightness;
+ this.pixPower = pixPower;
+ }
+
+ public void writeLogDataInfo(OutputStreamWriter out) throws IOException {
+ out.write("OLED-brightness " + brightness + "\n");
+ out.write("OLED-pix-power " + pixPower + "\n");
+ out.write("OLED-screen-on " + screenOn + "\n");
+ }
+ }
+
+ private static final String TAG = "OLED";
+ private static final String[] BACKLIGHT_BRIGHTNESS_FILES = {
+ "/sys/class/leds/lcd-backlight/brightness",
+ "/sys/devices/virtual/leds/lcd-backlight/brightness",
+ "/sys/devices/platform/trout-backlight.0/leds/lcd-backlight/brightness",
+ };
+
+ private Context context;
+ private ForegroundDetector foregroundDetector;
+ private BroadcastReceiver broadcastReceiver;
+ private boolean screenOn;
+
+ private File frameBufferFile;
+
+ private int screenWidth;
+ private int screenHeight;
+
+ private static final int NUMBER_OF_SAMPLES = 500;
+ private int[] samples;
+
+ private String brightnessFile;
+
+ /* Coefficients pre-computed for pix power calculations.
+ */
+ private double rcoef;
+ private double gcoef;
+ private double bcoef;
+ private double modul_coef;
+
+ public OLED(Context context, PhoneConstants constants) {
+ this.context = context;
+ screenOn = true;
+
+ foregroundDetector = new ForegroundDetector((ActivityManager)
+ context.getSystemService(context.ACTIVITY_SERVICE));
+ broadcastReceiver = new BroadcastReceiver() {
+ public void onReceive(Context context, Intent intent) {
+ synchronized(this) {
+ if(intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
+ screenOn = false;
+ } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
+ screenOn = true;
+ }
+ }
+ };
+ };
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+ intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+ context.registerReceiver(broadcastReceiver, intentFilter);
+
+ frameBufferFile = new File("/dev/fb0");
+ if(!frameBufferFile.exists()) {
+ frameBufferFile = new File("/dev/graphics/fb0");
+ }
+ if(frameBufferFile.exists()) try {
+ /* Check if we already have permission to read the frame buffer. */
+ boolean readOk = false;
+ try {
+ RandomAccessFile fin = new RandomAccessFile(frameBufferFile, "r");
+ int b = fin.read();
+ fin.close();
+ readOk = true;
+ } catch(IOException e) {
+ }
+ /* Don't have permission, try to change permission as root. */
+ if(!readOk) {
+ java.lang.Process p = Runtime.getRuntime().exec("su");
+ DataOutputStream os = new DataOutputStream(p.getOutputStream());
+ os.writeBytes("chown " + android.os.Process.myUid() +
+ " " + frameBufferFile.getAbsolutePath() + "\n");
+ os.writeBytes("chown app_" + (android.os.Process.myUid() -
+ SystemInfo.AID_APP) +
+ " " + frameBufferFile.getAbsolutePath() + "\n");
+ os.writeBytes("chmod 660 " + frameBufferFile.getAbsolutePath() + "\n");
+ os.writeBytes("exit\n");
+ os.flush();
+ p.waitFor();
+ if(p.exitValue() != 0) {
+ Log.i(TAG, "failed to change permissions on frame buffer");
+ }
+ }
+ } catch (InterruptedException e) {
+ Log.i(TAG, "changing permissions on frame buffer interrupted");
+ } catch (IOException e) {
+ Log.i(TAG, "unexpected exception while changing permission on " +
+ "frame buffer");
+ e.printStackTrace();
+ }
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager windowManager =
+ (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ windowManager.getDefaultDisplay().getMetrics(metrics);
+ screenWidth = metrics.widthPixels;
+ screenHeight = metrics.heightPixels;
+
+ Random r = new Random();
+ samples = new int[NUMBER_OF_SAMPLES];
+ for(int i = 0; i < NUMBER_OF_SAMPLES; i++) {
+ int a = screenWidth * screenHeight * i / NUMBER_OF_SAMPLES;
+ int b = screenWidth * screenHeight * (i + 1) / NUMBER_OF_SAMPLES;
+ samples[i] = a + r.nextInt(b - a);
+ }
+
+ double[] channel = constants.oledChannelPower();
+ rcoef = channel[0] / 255 / 255;
+ gcoef = channel[1] / 255 / 255;
+ bcoef = channel[2] / 255 / 255;
+ modul_coef = constants.oledModulation() / 255 / 255 / 3 / 3;
+
+ for(int i = 0; i < BACKLIGHT_BRIGHTNESS_FILES.length; i++) {
+ if(new File(BACKLIGHT_BRIGHTNESS_FILES[i]).exists()) {
+ brightnessFile = BACKLIGHT_BRIGHTNESS_FILES[i];
+ }
+ }
+ }
+
+ @Override
+ protected void onExit() {
+ context.unregisterReceiver(broadcastReceiver);
+ super.onExit();
+ }
+
+ @Override
+ public IterationData calculateIteration(long iteration) {
+ IterationData result = IterationData.obtain();
+
+ boolean screen;
+ synchronized(this) {
+ screen = screenOn;
+ }
+
+ int brightness;
+ if(brightnessFile != null) {
+ brightness = (int)SystemInfo.getInstance()
+ .readLongFromFile(brightnessFile);
+ } else {
+ try {
+ brightness = Settings.System.getInt(context.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS);
+ } catch(Settings.SettingNotFoundException ex) {
+ Log.w(TAG, "Could not retrieve brightness information");
+ return result;
+ }
+ }
+ if(brightness < 0 || 255 < brightness) {
+ Log.w(TAG, "Could not retrieve brightness information");
+ return result;
+ }
+
+ double pixPower = 0;
+ if(screen && frameBufferFile.exists()) {
+ if(NativeLoader.jniLoaded()) {
+ pixPower = getScreenPixPower(rcoef, gcoef, bcoef, modul_coef);
+ } else try {
+ RandomAccessFile fin = new RandomAccessFile(frameBufferFile, "r");
+
+ for(int x : samples) {
+ fin.seek(x * 4);
+ int px = fin.readInt();
+ int b = px >> 8 & 0xFF;
+ int g = px >> 16 & 0xFF;
+ int r = px >> 24 & 0xFF;
+
+ /* Calculate the power usage of this one pixel if it were at full
+ * brightness. Linearly scale by brightness to get true power
+ * consumption. To calculate whole screen compute average of sampled
+ * region and multiply by number of pixels.
+ */
+ int modul_val = r + g + b;
+ pixPower += rcoef * (r * r) + gcoef * (g * g) + bcoef * (b * b) -
+ modul_coef * (modul_val * modul_val);
+ }
+ fin.close();
+ } catch(FileNotFoundException e) {
+ pixPower = -1;
+ } catch(IOException e) {
+ pixPower = -1;
+ e.printStackTrace();
+ }
+ if(pixPower >= 0) {
+ pixPower *= 1.0 * screenWidth * screenHeight / NUMBER_OF_SAMPLES;
+ }
+ }
+
+ OledData data = OledData.obtain();
+ if(!screen) {
+ data.init();
+ } else {
+ data.init(brightness, pixPower);
+ }
+ result.setPowerData(data);
+
+ if(screen) {
+ OledData uidData = OledData.obtain();
+ uidData.init(brightness, pixPower);
+ result.addUidPowerData(foregroundDetector.getForegroundUid(), uidData);
+ }
+
+ return result;
+ }
+
+ @Override
+ public boolean hasUidInformation() {
+ return true;
+ }
+
+ @Override
+ public String getComponentName() {
+ return "OLED";
+ }
+
+ public static native double getScreenPixPower(double rcoef, double gcoef,
+ double bcoef, double modul_coef);
+}
diff --git a/src/edu/umich/PowerTutor/components/PowerComponent.java b/src/edu/umich/PowerTutor/components/PowerComponent.java
new file mode 100644
index 0000000..0b6eadd
--- /dev/null
+++ b/src/edu/umich/PowerTutor/components/PowerComponent.java
@@ -0,0 +1,142 @@
+/*
+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.components;
+
+import edu.umich.PowerTutor.service.IterationData;
+
+import android.os.SystemClock;
+import android.util.Log;
+
+public abstract class PowerComponent extends Thread {
+ private final String TAG = "PowerComponent";
+
+ /* Extending classes need to override the calculateIteration function. It
+ * should calculate the data point for the given component in a timely
+ * manner (under 1 second, longer times will cause data to be missed).
+ * The iteration parameter can be ignored in most cases.
+ */
+ protected abstract IterationData calculateIteration(long iteration);
+
+ /* Extending classes should provide a recognizable name for this component
+ * to be used when writing to logs.
+ */
+ public abstract String getComponentName();
+
+ /* Returns true if this component collects usage information per uid.
+ */
+ public boolean hasUidInformation() {
+ return false;
+ }
+
+ /* Called when the thread running this interface is asked to exit.
+ */
+ protected void onExit() {
+ }
+
+ /* In general we should only need to buffer two data elements.
+ */
+ private IterationData data1;
+ private IterationData data2;
+ private long iteration1;
+ private long iteration2;
+
+ protected long beginTime;
+ protected long iterationInterval;
+
+ public PowerComponent() {
+ setDaemon(true);
+ }
+
+ /* This is called once at the begginning of the daemon loop.
+ */
+ public void init(long beginTime, long iterationInterval) {
+ this.beginTime = beginTime;
+ this.iterationInterval = iterationInterval;
+ data1 = data2 = null;
+ iteration1 = iteration2 = -1;
+ }
+
+ /* Runs the daemon loop that collects data for this component. */
+ public void run() {
+ android.os.Process.setThreadPriority(
+ android.os.Process.THREAD_PRIORITY_MORE_FAVORABLE);
+ for(long iter = 0; !Thread.interrupted(); ) {
+ /* Hand off to the client class to actually calculate the information
+ * we want for this component.
+ */
+ IterationData data = calculateIteration(iter);
+ if(data != null) {
+ synchronized(this) {
+ if(iteration1 < iteration2) {
+ iteration1 = iter;
+ data1 = data;
+ } else {
+ iteration2 = iter;
+ data2 = data;
+ }
+ }
+ }
+ if(interrupted()) {
+ break;
+ }
+
+ long curTime = SystemClock.elapsedRealtime();
+ /* Compute the next iteration that we can make the start of. */
+ long oldIter = iter;
+ iter = (long)Math.max(iter + 1,
+ 1 + (curTime - beginTime) / iterationInterval);
+ if(oldIter + 1 != iter) {
+ Log.w(TAG, "[" + getComponentName() + "] Had to skip from iteration " +
+ oldIter + " to " + iter);
+ }
+ /* Sleep until the next iteration completes. */
+ try {
+ sleep(beginTime + iter * iterationInterval - curTime);
+ } catch(InterruptedException e) {
+ break;
+ }
+ }
+ onExit();
+ }
+
+ /* Returns the data point for the given iteration. This method will be called
+ with a strictly increasing iteration parameter.
+ */
+ public IterationData getData(long iteration) {
+ synchronized(this) {
+ IterationData ret = null;
+ if(iteration == iteration1) ret = data1;
+ if(iteration == iteration2) ret = data2;
+ if(iteration1 <= iteration) {
+ data1 = null;
+ iteration1 = -1;
+ }
+ if(iteration2 <= iteration) {
+ data2 = null;
+ iteration2 = -1;
+ }
+ if(ret == null) {
+ Log.w(TAG, "[" + getComponentName() + "] Could not find data for " +
+ "requested iteration");
+ }
+ return ret;
+ }
+ }
+}
diff --git a/src/edu/umich/PowerTutor/components/Sensors.java b/src/edu/umich/PowerTutor/components/Sensors.java
new file mode 100644
index 0000000..e0917fb
--- /dev/null
+++ b/src/edu/umich/PowerTutor/components/Sensors.java
@@ -0,0 +1,217 @@
+/*
+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.components;
+
+import edu.umich.PowerTutor.PowerNotifications;
+import edu.umich.PowerTutor.service.IterationData;
+import edu.umich.PowerTutor.service.PowerData;
+import edu.umich.PowerTutor.util.NotificationService;
+import edu.umich.PowerTutor.util.Recycler;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+import android.content.Context;
+import android.hardware.SensorManager;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class Sensors extends PowerComponent {
+ private final String TAG = "Sensors";
+ public static final int MAX_SENSORS = 10;
+
+ public static class SensorData extends PowerData {
+ private static Recycler<SensorData> recycler = new Recycler<SensorData>();
+
+ public static SensorData obtain() {
+ SensorData result = recycler.obtain();
+ if(result != null) return result;
+ return new SensorData();
+ }
+
+ @Override
+ public void recycle() {
+ recycler.recycle(this);
+ }
+
+ public double[] onTime;
+
+ private SensorData() {
+ onTime = new double[MAX_SENSORS];
+ }
+
+ public void writeLogDataInfo(OutputStreamWriter out) throws IOException {
+ StringBuilder res = new StringBuilder();
+ for(int i = 0; i < MAX_SENSORS; i++) {
+ if(onTime[i] > 1e-7) {
+ res.append("Sensors-time ").append(i).append(" ")
+ .append(onTime[i]).append("\n");
+ }
+ }
+ out.write(res.toString());
+ }
+ }
+
+ private Context context;
+ private SensorManager sensorManager;
+ private PowerNotifications sensorHook;
+
+ private SensorStateKeeper sensorState;
+ private SparseArray<SensorStateKeeper> uidStates;
+
+ public Sensors(Context context) {
+ this.context = context;
+ sensorState = new SensorStateKeeper();
+ uidStates = new SparseArray<SensorStateKeeper>();
+
+ if(!NotificationService.available()) {
+ Log.w(TAG, "Sensor component created although no notification service " +
+ "available to receive sensor usage information");
+ return;
+ }
+ sensorManager = (SensorManager)context.getSystemService(
+ Context.SENSOR_SERVICE);
+ sensorHook = new NotificationService.DefaultReceiver() {
+ public void noteStartSensor(int uid, int sensor) {
+ if(sensor < 0 || MAX_SENSORS <= sensor) {
+ Log.w(TAG, "Received sensor outside of accepted range");
+ return;
+ }
+ synchronized(sensorState) {
+ sensorState.startSensor(sensor);
+ SensorStateKeeper uidState = uidStates.get(uid);
+ if(uidState == null) {
+ uidState = new SensorStateKeeper();
+ uidStates.put(uid, uidState);
+ }
+ uidState.startSensor(sensor);
+ }
+ }
+
+ public void noteStopSensor(int uid, int sensor) {
+ if(sensor < 0 || MAX_SENSORS <= sensor) {
+ Log.w(TAG, "Received sensor outside of accepted range");
+ return;
+ }
+ synchronized(sensorState) {
+ sensorState.stopSensor(sensor);
+ SensorStateKeeper uidState = uidStates.get(uid);
+ if(uidState == null) {
+ uidState = new SensorStateKeeper();
+ uidStates.put(uid, uidState);
+ }
+ uidState.stopSensor(sensor);
+ }
+ }
+ };
+ NotificationService.addHook(sensorHook);
+ }
+
+ @Override
+ protected void onExit() {
+ super.onExit();
+ NotificationService.removeHook(sensorHook);
+ }
+
+ @Override
+ public IterationData calculateIteration(long iteration) {
+ IterationData result = IterationData.obtain();
+ synchronized(sensorState) {
+ SensorData globalData = SensorData.obtain();
+ sensorState.setupSensorTimes(globalData.onTime, iterationInterval);
+ result.setPowerData(globalData);
+
+ for(int i = 0; i < uidStates.size(); i++) {
+ int uid = uidStates.keyAt(i);
+ SensorStateKeeper uidState = uidStates.valueAt(i);
+ SensorData uidData = SensorData.obtain();
+ uidState.setupSensorTimes(uidData.onTime, iterationInterval);
+ result.addUidPowerData(uid, uidData);
+
+ if(uidState.sensorsOn() == 0) {
+ uidStates.remove(uid);
+ i--;
+ }
+ }
+ }
+ return result;
+ }
+
+ private static class SensorStateKeeper {
+ private int[] nesting;
+ private long[] times;
+ private long lastTime;
+ private int count;
+
+ public SensorStateKeeper() {
+ nesting = new int[MAX_SENSORS];
+ times = new long[MAX_SENSORS];
+ lastTime = SystemClock.elapsedRealtime();
+ }
+
+ public void startSensor(int sensor) {
+ if(nesting[sensor]++ == 0) {
+ times[sensor] -= SystemClock.elapsedRealtime() - lastTime;
+ count++;
+ }
+ }
+
+ public void stopSensor(int sensor) {
+ if(nesting[sensor] == 0) {
+ return;
+ } else if(--nesting[sensor] == 0) {
+ times[sensor] += SystemClock.elapsedRealtime() - lastTime;
+ count--;
+ }
+ }
+
+ public int sensorsOn() {
+ return count;
+ }
+
+ public void setupSensorTimes(double[] sensorTimes, long iterationInterval) {
+ long now = SystemClock.elapsedRealtime();
+ long div = now - lastTime;
+ if(div <= 0) div = 1;
+ for(int i = 0; i < MAX_SENSORS; i++) {
+ sensorTimes[i] = 1.0 * (times[i] +
+ (nesting[i] > 0 ? now - lastTime : 0)) / div;
+ times[i] = 0;
+ }
+ lastTime = now;
+ }
+ }
+
+ @Override
+ public boolean hasUidInformation() {
+ return true;
+ }
+
+ @Override
+ public String getComponentName() {
+ return "Sensors";
+ }
+}
diff --git a/src/edu/umich/PowerTutor/components/Threeg.java b/src/edu/umich/PowerTutor/components/Threeg.java
new file mode 100644
index 0000000..88d8736
--- /dev/null
+++ b/src/edu/umich/PowerTutor/components/Threeg.java
@@ -0,0 +1,398 @@
+/*
+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.components;
+
+import edu.umich.PowerTutor.phone.PhoneConstants;
+import edu.umich.PowerTutor.service.IterationData;
+import edu.umich.PowerTutor.service.PowerData;
+import edu.umich.PowerTutor.service.PowerEstimator;
+import edu.umich.PowerTutor.util.Recycler;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+import android.content.Context;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
+import android.telephony.TelephonyManager;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.RandomAccessFile;
+
+public class Threeg extends PowerComponent {
+ public static class ThreegData extends PowerData {
+ private static Recycler<ThreegData> recycler = new Recycler<ThreegData>();
+
+ public static ThreegData obtain() {
+ ThreegData result = recycler.obtain();
+ if(result != null) return result;
+ return new ThreegData();
+ }
+
+ @Override
+ public void recycle() {
+ recycler.recycle(this);
+ }
+
+ public boolean threegOn;
+ public long packets;
+ public long uplinkBytes;
+ public long downlinkBytes;
+ public int powerState;
+ public String oper;
+
+ private ThreegData() {
+ }
+
+ public void init() {
+ threegOn = false;
+ }
+
+ public void init(long packets, long uplinkBytes, long downlinkBytes,
+ int powerState, String oper) {
+ threegOn = true;
+ this.packets = packets;
+ this.uplinkBytes = uplinkBytes;
+ this.downlinkBytes = downlinkBytes;
+ this.powerState = powerState;
+ this.oper = oper;
+ }
+
+ public void writeLogDataInfo(OutputStreamWriter out) throws IOException {
+ StringBuilder res = new StringBuilder();
+ res.append("3G-on ").append(threegOn).append("\n");
+ if(threegOn) {
+ res.append("3G-uplinkBytes ").append(uplinkBytes)
+ .append("\n3G-downlinkBytes ").append(downlinkBytes)
+ .append("\n3G-packets ").append(packets)
+ .append("\n3G-state ").append(Threeg.POWER_STATE_NAMES[powerState])
+ .append("\n3G-oper ").append(oper)
+ .append("\n");
+ }
+ out.write(res.toString());
+ }
+ }
+
+ public static final int POWER_STATE_IDLE = 0;
+ public static final int POWER_STATE_FACH = 1;
+ public static final int POWER_STATE_DCH = 2;
+ public static final String[] POWER_STATE_NAMES = {"IDLE", "FACH", "DCH"};
+
+ private static final String TAG = "Threeg";
+
+ private PhoneConstants phoneConstants;
+ private TelephonyManager telephonyManager;
+ private SystemInfo sysInfo;
+
+ private String oper;
+ private int dchFachDelay;
+ private int fachIdleDelay;
+ private int uplinkQueueSize;
+ private int downlinkQueueSize;
+
+ private int[] lastUids;
+ private ThreegStateKeeper threegState;
+ private SparseArray<ThreegStateKeeper> uidStates;
+
+ private String transPacketsFile;
+ private String readPacketsFile;
+ private String readBytesFile;
+ private String transBytesFile;
+ private File uidStatsFolder;
+
+ public Threeg(Context context, PhoneConstants phoneConstants) {
+ this.phoneConstants = phoneConstants;
+ telephonyManager = (TelephonyManager)context.getSystemService(
+ Context.TELEPHONY_SERVICE);
+
+ String interfaceName = phoneConstants.threegInterface();
+ threegState = new ThreegStateKeeper();
+ uidStates = new SparseArray<ThreegStateKeeper>();
+ transPacketsFile = "/sys/devices/virtual/net/" +
+ interfaceName + "/statistics/tx_packets";
+ readPacketsFile = "/sys/devices/virtual/net/" +
+ interfaceName + "/statistics/rx_packets";
+ readBytesFile = "/sys/devices/virtual/net/" +
+ interfaceName + "/statistics/rx_bytes";
+ transBytesFile = "/sys/devices/virtual/net/" +
+ interfaceName + "/statistics/tx_bytes";
+ uidStatsFolder = new File("/proc/uid_stat");
+ sysInfo = SystemInfo.getInstance();
+ }
+
+ @Override
+ public IterationData calculateIteration(long iteration) {
+ IterationData result = IterationData.obtain();
+
+ int netType = telephonyManager.getNetworkType();
+
+ if((netType != TelephonyManager.NETWORK_TYPE_UMTS &&
+ netType != 8/* TelephonyManager.NETWORK_TYPE_HSDPA */)) {
+ // TODO: Actually get models for the different network types.
+ netType = TelephonyManager.NETWORK_TYPE_UMTS;
+ }
+
+ if(telephonyManager.getDataState() != TelephonyManager.DATA_CONNECTED ||
+ (netType != TelephonyManager.NETWORK_TYPE_UMTS &&
+ netType != 8/* TelephonyManager.NETWORK_TYPE_HSDPA */)) {
+ /* We need to allow the real iterface state keeper to reset it's state
+ * so that the next update it knows it's coming back from an off state.
+ * We also need to clear all the uid information.
+ */
+ oper = null;
+ threegState.interfaceOff();
+ uidStates.clear();
+
+ ThreegData data = ThreegData.obtain();
+ data.init();
+ result.setPowerData(data);
+ return result;
+ }
+
+ if(oper == null) {
+ oper = telephonyManager.getNetworkOperatorName();
+ dchFachDelay = phoneConstants.threegDchFachDelay(oper);
+ fachIdleDelay = phoneConstants.threegFachIdleDelay(oper);
+ uplinkQueueSize = phoneConstants.threegUplinkQueue(oper);
+ downlinkQueueSize = phoneConstants.threegDownlinkQueue(oper);
+ }
+
+ long transmitPackets = readLongFromFile(transPacketsFile);
+ long receivePackets = readLongFromFile(readPacketsFile);
+ long transmitBytes = readLongFromFile(transBytesFile);
+ long receiveBytes = readLongFromFile(readBytesFile);
+ if(transmitBytes == -1 || receiveBytes == -1) {
+ /* Couldn't read interface data files. */
+ Log.w(TAG, "Failed to read packet and byte counts from wifi interface");
+ return result;
+ }
+
+ if(threegState.isInitialized()) {
+ threegState.updateState(transmitPackets, receivePackets,
+ transmitBytes, receiveBytes,
+ dchFachDelay, fachIdleDelay,
+ uplinkQueueSize, downlinkQueueSize);
+ ThreegData data = ThreegData.obtain();
+ data.init(threegState.getPackets(), threegState.getUplinkBytes(),
+ threegState.getDownlinkBytes(), threegState.getPowerState(),
+ oper);
+ result.setPowerData(data);
+ } else {
+ threegState.updateState(transmitPackets, receivePackets,
+ transmitBytes, receiveBytes,
+ dchFachDelay, fachIdleDelay,
+ uplinkQueueSize, downlinkQueueSize);
+ }
+
+ lastUids = sysInfo.getUids(lastUids);
+ if(lastUids != null) for(int uid : lastUids) {
+ if(uid == -1) {
+ continue;
+ }
+ try {
+ ThreegStateKeeper uidState = uidStates.get(uid);
+ if(uidState == null) {
+ uidState = new ThreegStateKeeper();
+ uidStates.put(uid, uidState);
+ }
+
+ if(!uidState.isStale()) {
+ /* We use a huerstic here so that we don't poll for uids that haven't
+ * had much activity recently.
+ */
+ continue;
+ }
+
+ /* These read operations are the expensive part of polling. */
+ receiveBytes = readLongFromFile("/proc/uid_stat/" + uid + "/tcp_rcv");
+ transmitBytes = readLongFromFile("/proc/uid_stat/" + uid + "/tcp_snd");
+
+ if(receiveBytes == -1 || transmitBytes == -1) {
+ Log.w(TAG, "Failed to read uid read/write byte counts");
+ } else if(uidState.isInitialized()) {
+ uidState.updateState(-1, -1, transmitBytes, receiveBytes,
+ dchFachDelay, fachIdleDelay,
+ uplinkQueueSize, downlinkQueueSize);
+
+ if(uidState.getUplinkBytes() + uidState.getDownlinkBytes() != 0 ||
+ uidState.getPowerState() != POWER_STATE_IDLE) {
+ ThreegData uidData = ThreegData.obtain();
+ uidData.init(uidState.getPackets(),
+ uidState.getUplinkBytes(), uidState.getDownlinkBytes(),
+ uidState.getPowerState(), oper);
+ result.addUidPowerData(uid, uidData);
+ }
+ } else {
+ uidState.updateState(-1, -1, transmitBytes, receiveBytes,
+ dchFachDelay, fachIdleDelay,
+ uplinkQueueSize, downlinkQueueSize);
+ }
+ } catch(NumberFormatException e) {
+ Log.w(TAG, "Non-uid files in /proc/uid_stat");
+ }
+ }
+
+ return result;
+ }
+
+ private static class ThreegStateKeeper {
+ private long lastTransmitPackets;
+ private long lastReceivePackets;
+ private long lastTransmitBytes;
+ private long lastReceiveBytes;
+ private long lastTime;
+
+ private long deltaPackets;
+ private long deltaUplinkBytes;
+ private long deltaDownlinkBytes;
+
+ private int powerState;
+ private int stateTime;
+
+ private long inactiveTime;
+
+ public ThreegStateKeeper() {
+ lastTransmitBytes = lastReceiveBytes = lastTime = -1;
+ deltaUplinkBytes = deltaDownlinkBytes = -1;
+ powerState = POWER_STATE_IDLE;
+ stateTime = 0;
+ inactiveTime = 0;
+ }
+
+ public void interfaceOff() {
+ lastTime = SystemClock.elapsedRealtime();
+ powerState = POWER_STATE_IDLE;
+ }
+
+ public boolean isInitialized() {
+ return lastTime != -1;
+ }
+
+ public void updateState(long transmitPackets, long receivePackets,
+ long transmitBytes, long receiveBytes,
+ int dchFachDelay, int fachIdleDelay,
+ int uplinkQueueSize, int downlinkQueueSize) {
+ long curTime = SystemClock.elapsedRealtime();
+ if(lastTime != -1 && curTime > lastTime) {
+ double deltaTime = curTime - lastTime;
+ deltaPackets = transmitPackets + receivePackets -
+ lastTransmitPackets - lastReceivePackets;
+ deltaUplinkBytes = transmitBytes - lastTransmitBytes;
+ deltaDownlinkBytes = receiveBytes - lastReceiveBytes;
+ boolean inactive = deltaUplinkBytes == 0 && deltaDownlinkBytes == 0;
+ inactiveTime = inactive ? inactiveTime + curTime - lastTime : 0;
+
+ // TODO: make this always work.
+ int timeMult = 1;
+ if(1000 % PowerEstimator.ITERATION_INTERVAL != 0) {
+ Log.w(TAG,
+ "Cannot handle iteration intervals that are a factor of 1 second");
+ } else {
+ timeMult = 1000 / PowerEstimator.ITERATION_INTERVAL;
+ }
+
+ switch(powerState) {
+ case POWER_STATE_IDLE:
+ if(!inactive) {
+ powerState = POWER_STATE_FACH;
+ }
+ break;
+ case POWER_STATE_FACH:
+ if(inactive) {
+ stateTime++;
+ if(stateTime >= fachIdleDelay * timeMult) {
+ stateTime = 0;
+ powerState = POWER_STATE_IDLE;
+ }
+ } else {
+ stateTime = 0;
+ if(deltaUplinkBytes > 0 ||
+ deltaDownlinkBytes > 0) {
+ powerState = POWER_STATE_DCH;
+ }
+ }
+ break;
+ default: // case POWER_STATE_DCH:
+ if(inactive) {
+ stateTime++;
+ if(stateTime >= dchFachDelay * timeMult) {
+ stateTime = 0;
+ powerState = POWER_STATE_FACH;
+ }
+ } else {
+ stateTime = 0;
+ }
+ }
+ }
+ lastTime = curTime;
+ lastTransmitPackets = transmitPackets;
+ lastReceivePackets = receivePackets;
+ lastTransmitBytes = transmitBytes;
+ lastReceiveBytes = receiveBytes;
+ }
+
+ public int getPowerState() {
+ return powerState;
+ }
+
+ public long getPackets() {
+ return deltaPackets;
+ }
+
+ public long getUplinkBytes() {
+ return deltaUplinkBytes;
+ }
+
+ public long getDownlinkBytes() {
+ return deltaDownlinkBytes;
+ }
+
+ /* The idea here is that we don't want to have to read uid information
+ * every single iteration for each uid as it just takes too long. So here
+ * we are designing a hueristic that helps us avoid polling for too many
+ * uids.
+ */
+ public boolean isStale() {
+ if(powerState != POWER_STATE_IDLE) return true;
+ long curTime = SystemClock.elapsedRealtime();
+ return curTime - lastTime > (long)Math.min(10000, inactiveTime);
+ }
+ }
+
+ private final static byte[] buf = new byte[16];
+
+ private long readLongFromFile(String filePath) {
+ return sysInfo.readLongFromFile(filePath);
+ }
+
+ @Override
+ public boolean hasUidInformation() {
+ return uidStatsFolder.exists();
+ }
+
+ @Override
+ public String getComponentName() {
+ return "3G";
+ }
+}
diff --git a/src/edu/umich/PowerTutor/components/Wifi.java b/src/edu/umich/PowerTutor/components/Wifi.java
new file mode 100644
index 0000000..0e10292
--- /dev/null
+++ b/src/edu/umich/PowerTutor/components/Wifi.java
@@ -0,0 +1,418 @@
+/*
+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.components;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+
+import android.content.Context;
+import android.net.wifi.WifiManager;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
+import edu.umich.PowerTutor.phone.PhoneConstants;
+import edu.umich.PowerTutor.service.IterationData;
+import edu.umich.PowerTutor.service.PowerData;
+import edu.umich.PowerTutor.util.Recycler;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+public class Wifi extends PowerComponent {
+ public static class WifiData extends PowerData {
+ private static Recycler<WifiData> recycler = new Recycler<WifiData>();
+
+ public static WifiData obtain() {
+ WifiData result = recycler.obtain();
+ if(result != null) return result;
+ return new WifiData();
+ }
+
+ @Override
+ public void recycle() {
+ recycler.recycle(this);
+ }
+
+ public boolean wifiOn;
+ public double packets;
+ public long uplinkBytes;
+ public long downlinkBytes;
+ public double uplinkRate;
+ public double linkSpeed;
+ public int powerState;
+
+ private WifiData() {
+ }
+
+ public void init(double packets, long uplinkBytes, long downlinkBytes,
+ double uplinkRate, double linkSpeed, int powerState) {
+ wifiOn = true;
+ this.packets = packets;
+ this.uplinkBytes = uplinkBytes;
+ this.downlinkBytes = downlinkBytes;
+ this.uplinkRate = uplinkRate;
+ this.linkSpeed = linkSpeed;
+ this.powerState = powerState;
+ }
+
+ public void init() {
+ wifiOn = false;
+ }
+
+ public void writeLogDataInfo(OutputStreamWriter out) throws IOException {
+ StringBuilder res = new StringBuilder();
+ res.append("Wifi-on ").append(wifiOn).append("\n");
+ if(wifiOn) {
+ res.append("Wifi-packets ").append((long)Math.round(packets))
+ .append("\nWifi-uplinkBytes ").append(uplinkBytes)
+ .append("\nWifi-downlinkBytes ").append(downlinkBytes)
+ .append("\nWifi-uplink ").append((long)Math.round(uplinkRate))
+ .append("\nWifi-speed ").append((long)Math.round(linkSpeed))
+ .append("\nWifi-state ").append(Wifi.POWER_STATE_NAMES[powerState])
+ .append("\n");
+ }
+ out.write(res.toString());
+ }
+ }
+
+ public static final int POWER_STATE_LOW = 0;
+ public static final int POWER_STATE_HIGH = 1;
+ public static final String[] POWER_STATE_NAMES = {"LOW", "HIGH"};
+
+ private static final String TAG = "Wifi";
+
+ private PhoneConstants phoneConstants;
+ private WifiManager wifiManager;
+ private SystemInfo sysInfo;
+
+ private long lastLinkSpeed;
+ private int[] lastUids;
+ private WifiStateKeeper wifiState;
+ private SparseArray<WifiStateKeeper> uidStates;
+
+ private String transPacketsFile;
+ private String readPacketsFile;
+ private String transBytesFile;
+ private String readBytesFile;
+ private File uidStatsFolder;
+
+ public Wifi(Context context, PhoneConstants phoneConstants) {
+ this.phoneConstants = phoneConstants;
+ wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
+ sysInfo = SystemInfo.getInstance();
+
+ /* Try to grab the interface name. If we can't find it will take a wild
+ * stab in the dark.
+ */
+ String interfaceName = SystemInfo.getInstance().getProperty("wifi.interface");
+ if(interfaceName == null) interfaceName = "eth0";
+
+ lastLinkSpeed = -1;
+ wifiState = new WifiStateKeeper(phoneConstants.wifiHighLowTransition(),
+ phoneConstants.wifiLowHighTransition());
+ uidStates = new SparseArray<WifiStateKeeper>();
+ transPacketsFile = "/sys/devices/virtual/net/" +
+ interfaceName + "/statistics/tx_packets";
+ readPacketsFile = "/sys/devices/virtual/net/" +
+ interfaceName + "/statistics/rx_packets";
+ transBytesFile = "/sys/devices/virtual/net/" +
+ interfaceName + "/statistics/tx_bytes";
+ readBytesFile = "/sys/devices/virtual/net/" +
+ interfaceName + "/statistics/rx_bytes";
+ uidStatsFolder = new File("/proc/uid_stat");
+ }
+
+ @Override
+ public IterationData calculateIteration(long iteration) {
+ IterationData result = IterationData.obtain();
+
+ int wifiStateFlag = wifiManager.getWifiState();
+ if(wifiStateFlag != WifiManager.WIFI_STATE_ENABLED &&
+ wifiStateFlag != WifiManager.WIFI_STATE_DISABLING) {
+ /* We need to allow the real iterface state keeper to reset it's state
+ * so that the next update it knows it's coming back from an off state.
+ * We also need to clear all the uid information.
+ */
+ wifiState.interfaceOff();
+ uidStates.clear();
+ lastLinkSpeed = -1;
+
+ WifiData data = WifiData.obtain();
+ data.init();
+ result.setPowerData(data);
+ return result;
+ }
+
+ long transmitPackets = sysInfo.readLongFromFile(transPacketsFile);
+ long receivePackets = sysInfo.readLongFromFile(readPacketsFile);
+ long transmitBytes = sysInfo.readLongFromFile(transBytesFile);
+ long receiveBytes = sysInfo.readLongFromFile(readBytesFile);
+ if(transmitPackets == -1 || receivePackets == -1 ||
+ transmitBytes == -1 || receiveBytes == -1) {
+ /* Couldn't read interface data files. */
+ Log.w(TAG, "Failed to read packet and byte counts from wifi interface");
+ return result;
+ }
+
+ /* Update the link speed every 15 seconds as pulling the WifiInfo structure
+ * from WifiManager is a little bit expensive. This isn't really something
+ * that is likely to change very frequently anyway.
+ */
+ if(iteration % 15 == 0 || lastLinkSpeed == -1) {
+ lastLinkSpeed = wifiManager.getConnectionInfo().getLinkSpeed();
+ }
+ double linkSpeed = lastLinkSpeed;
+
+ if(wifiState.isInitialized()) {
+ wifiState.updateState(transmitPackets, receivePackets,
+ transmitBytes, receiveBytes);
+ WifiData data = WifiData.obtain();
+ data.init(wifiState.getPackets(), wifiState.getUplinkBytes(),
+ wifiState.getDownlinkBytes(), wifiState.getUplinkRate(),
+ linkSpeed, wifiState.getPowerState());
+ result.setPowerData(data);
+ } else {
+ wifiState.updateState(transmitPackets, receivePackets,
+ transmitBytes, receiveBytes);
+ }
+
+ lastUids = sysInfo.getUids(lastUids);
+ if(lastUids != null) for(int uid : lastUids) {
+ if(uid == -1) {
+ continue;
+ }
+ try {
+ WifiStateKeeper uidState = uidStates.get(uid);
+ if(uidState == null) {
+ uidState = new WifiStateKeeper(phoneConstants.wifiHighLowTransition(),
+ phoneConstants.wifiLowHighTransition());
+ uidStates.put(uid, uidState);
+ }
+
+ if(!uidState.isStale()) {
+ /* We use a huerstic here so that we don't poll for uids that haven't
+ * had much activity recently.
+ */
+ continue;
+ }
+
+ /* These read operations are the expensive part of polling. */
+ receiveBytes = sysInfo.readLongFromFile(
+ "/proc/uid_stat/" + uid + "/tcp_rcv");
+ transmitBytes = sysInfo.readLongFromFile(
+ "/proc/uid_stat/" + uid + "/tcp_snd");
+
+ if(receiveBytes == -1 || transmitBytes == -1) {
+ Log.w(TAG, "Failed to read uid read/write byte counts");
+ } else if(uidState.isInitialized()) {
+ /* We only have information about bytes received but what we really
+ * want is the number of packets received so we just have to
+ * estimate it.
+ */
+ long deltaTransmitBytes = transmitBytes - uidState.getTransmitBytes();
+ long deltaReceiveBytes = receiveBytes - uidState.getReceiveBytes();
+ long estimatedTransmitPackets = (long)Math.round(deltaTransmitBytes /
+ wifiState.getAverageTransmitPacketSize());
+ long estimatedReceivePackets = (long)Math.round(deltaReceiveBytes /
+ wifiState.getAverageReceivePacketSize());
+ if(deltaTransmitBytes > 0 && estimatedTransmitPackets == 0) {
+ estimatedTransmitPackets = 1;
+ }
+ if(deltaReceiveBytes > 0 && estimatedReceivePackets == 0) {
+ estimatedReceivePackets = 1;
+ }
+
+ boolean active = transmitBytes != uidState.getTransmitBytes() ||
+ receiveBytes != uidState.getReceiveBytes();
+ uidState.updateState(
+ uidState.getTransmitPackets() + estimatedTransmitPackets,
+ uidState.getReceivePackets() + estimatedReceivePackets,
+ transmitBytes, receiveBytes);
+
+ if(active) {
+ WifiData uidData = WifiData.obtain();
+ uidData.init(uidState.getPackets(), uidState.getUplinkBytes(),
+ uidState.getDownlinkBytes(), uidState.getUplinkRate(),
+ linkSpeed, uidState.getPowerState());
+ result.addUidPowerData(uid, uidData);
+ }
+ } else {
+ uidState.updateState(0, 0, transmitBytes, receiveBytes);
+ }
+ } catch(NumberFormatException e) {
+ Log.w(TAG, "Non-uid files in /proc/uid_stat");
+ }
+ }
+
+ return result;
+ }
+
+ private static class WifiStateKeeper {
+ private long lastTransmitPackets;
+ private long lastReceivePackets;
+ private long lastTransmitBytes;
+ private long lastReceiveBytes;
+ private long lastTime;
+
+ private int powerState;
+ private double lastPackets;
+ private double lastUplinkRate;
+ private double lastAverageTransmitPacketSize;
+ private double lastAverageReceivePacketSize;
+
+ private long deltaUplinkBytes;
+ private long deltaDownlinkBytes;
+
+ private double highLowTransition;
+ private double lowHighTransition;
+
+ private long inactiveTime;
+
+ public WifiStateKeeper(double highLowTransition, double lowHighTransition) {
+ this.highLowTransition = highLowTransition;
+ this.lowHighTransition = lowHighTransition;
+ lastTransmitPackets = lastReceivePackets = lastTransmitBytes =
+ lastTime = -1;
+ powerState = POWER_STATE_LOW;
+ lastPackets = lastUplinkRate = 0;
+ lastAverageTransmitPacketSize = 1000;
+ lastAverageReceivePacketSize = 1000;
+ inactiveTime = 0;
+ }
+
+ public void interfaceOff() {
+ lastTime = SystemClock.elapsedRealtime();
+ powerState = POWER_STATE_LOW;
+ }
+
+ public boolean isInitialized() {
+ return lastTime != -1;
+ }
+
+ public void updateState(long transmitPackets, long receivePackets,
+ long transmitBytes, long receiveBytes) {
+ long curTime = SystemClock.elapsedRealtime();
+ if(lastTime != -1 && curTime > lastTime) {
+ double deltaTime = curTime - lastTime;
+ lastUplinkRate = (transmitBytes - lastTransmitBytes) / 1024.0 *
+ 7.8125 / deltaTime;
+ lastPackets = receivePackets + transmitPackets -
+ lastReceivePackets - lastTransmitPackets;
+ deltaUplinkBytes = transmitBytes - lastTransmitBytes;
+ deltaDownlinkBytes = receiveBytes - lastReceiveBytes;
+ if(transmitPackets != lastTransmitPackets) {
+ lastAverageTransmitPacketSize = 0.9 * lastAverageTransmitPacketSize +
+ 0.1 * (transmitBytes - lastTransmitBytes) /
+ (transmitPackets - lastTransmitPackets);
+ }
+ if(receivePackets != lastReceivePackets) {
+ lastAverageReceivePacketSize = 0.9 * lastAverageReceivePacketSize +
+ 0.1 * (receiveBytes - lastReceiveBytes) /
+ (receivePackets - lastReceivePackets);
+ }
+
+ if(receiveBytes != lastReceiveBytes ||
+ transmitBytes != lastTransmitBytes) {
+ inactiveTime = 0;
+ } else {
+ inactiveTime += curTime - lastTime;
+ }
+
+ if(lastPackets < highLowTransition) {
+ powerState = POWER_STATE_LOW;
+ } else if(lastPackets > lowHighTransition) {
+ powerState = POWER_STATE_HIGH;
+ }
+ }
+ lastTime = curTime;
+ lastTransmitPackets = transmitPackets;
+ lastReceivePackets = receivePackets;
+ lastTransmitBytes = transmitBytes;
+ lastReceiveBytes = receiveBytes;
+ }
+
+ public int getPowerState() {
+ return powerState;
+ }
+
+ public double getPackets() {
+ return lastPackets;
+ }
+
+ public long getUplinkBytes() {
+ return deltaUplinkBytes;
+ }
+
+ public long getDownlinkBytes() {
+ return deltaDownlinkBytes;
+ }
+
+ public double getUplinkRate() {
+ return lastUplinkRate;
+ }
+
+ public double getAverageTransmitPacketSize() {
+ return lastAverageTransmitPacketSize;
+ }
+
+ public double getAverageReceivePacketSize() {
+ return lastAverageReceivePacketSize;
+ }
+
+ public long getTransmitPackets() {
+ return lastTransmitPackets;
+ }
+
+ public long getReceivePackets() {
+ return lastReceivePackets;
+ }
+
+ public long getTransmitBytes() {
+ return lastTransmitBytes;
+ }
+
+ public long getReceiveBytes() {
+ return lastReceiveBytes;
+ }
+
+ /* The idea here is that we don't want to have to read uid information
+ * every single iteration for each uid as it just takes too long. So here
+ * we are designing a hueristic that helps us avoid polling for too many
+ * uids.
+ */
+ public boolean isStale() {
+ long curTime = SystemClock.elapsedRealtime();
+ return curTime - lastTime > (long)Math.min(10000, inactiveTime);
+ }
+ }
+
+ private long readLongFromFile(String filePath) {
+ return sysInfo.readLongFromFile(filePath);
+ }
+
+ @Override
+ public boolean hasUidInformation() {
+ return uidStatsFolder.exists();
+ }
+
+ @Override
+ public String getComponentName() {
+ return "Wifi";
+ }
+}
diff --git a/src/edu/umich/PowerTutor/phone/DreamConstants.java b/src/edu/umich/PowerTutor/phone/DreamConstants.java
new file mode 100644
index 0000000..bf99925
--- /dev/null
+++ b/src/edu/umich/PowerTutor/phone/DreamConstants.java
@@ -0,0 +1,216 @@
+/*
+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.phone;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+
+import edu.umich.PowerTutor.components.Sensors;
+
+public class DreamConstants implements PhoneConstants {
+ protected static final String OPER_TMOBILE = "T - Mobile";
+ protected static final String OPER_ATT = "AT&T";
+
+ /* TODO: Figure out if this is really appropriate or how we should convert
+ * the sensor power ratings (in mA) to mW. I'm not sure we'll try to model
+ * these thing's power usage but for the developer it's definitely interesting
+ * to get some (perhaps rough?) idea of how much power sensors are using.
+ */
+ protected double BATTERY_VOLTAGE = 3.7;
+
+ public DreamConstants(Context context) {
+ SensorManager sensorManager = (SensorManager)context.getSystemService(
+ Context.SENSOR_SERVICE);
+ sensorPowerArray = new double[Sensors.MAX_SENSORS];
+ for(int i = 0; i < Sensors.MAX_SENSORS; i++) {
+ Sensor sensor = sensorManager.getDefaultSensor(i);
+ if(sensor != null) {
+ sensorPowerArray[i] = sensor.getPower() * BATTERY_VOLTAGE;
+ }
+ }
+ }
+
+ public String modelName() {
+ return "dream";
+ }
+
+ public double maxPower() {
+ return 2800;
+ }
+
+ public double lcdBrightness() {
+ return 2.40276;
+ }
+
+ public double lcdBacklight() {
+ return 121.4606 + 166.5;
+ }
+
+ public double oledBasePower() {
+ throw new RuntimeException("oledBasePower() called on device with no " +
+ "OLED display");
+ }
+
+ public double[] oledChannelPower() {
+ throw new RuntimeException("oledChannelPower() called on device with no " +
+ "OLED display");
+ }
+
+ public double oledModulation() {
+ throw new RuntimeException("oledModulation() called on device with no " +
+ "OLED display");
+ }
+
+ private static final double[] arrayCpuPowerRatios = {3.4169, 4.3388};
+ public double[] cpuPowerRatios() {
+ return arrayCpuPowerRatios;
+ }
+
+ private static final double[] arrayCpuFreqs = {245.36, 383.38};
+ public double[] cpuFreqs() {
+ return arrayCpuFreqs;
+ }
+
+ public double audioPower() {
+ return 384.62;
+ }
+
+ private static final double[] arrayGpsStatePower = {0.0, 173.55, 429.55};
+ public double[] gpsStatePower() {
+ return arrayGpsStatePower;
+ }
+
+ public double gpsSleepTime() {
+ return 6.0;
+ }
+
+ public double wifiLowPower() {
+ return 38.554;
+ }
+
+ public double wifiHighPower() {
+ return 720;
+ }
+
+ public double wifiLowHighTransition() {
+ return 15;
+ }
+
+ public double wifiHighLowTransition() {
+ return 8;
+ }
+
+ private static final double[] arrayWifiLinkRatios = {
+ 47.122645, 46.354821, 43.667437, 43.283525, 40.980053, 39.44422, 38.676581,
+ 34.069637, 29.462693, 20.248805, 11.034917, 6.427122
+ };
+ public double[] wifiLinkRatios() {
+ return arrayWifiLinkRatios;
+ }
+
+ private static final double[] arrayWifiLinkSpeeds = {
+ 1, 2, 5.5, 6, 9, 11, 12, 18, 24, 36, 48, 54
+ };
+ public double[] wifiLinkSpeeds() {
+ return arrayWifiLinkSpeeds;
+ }
+
+ public String threegInterface() {
+ return "rmnet0";
+ }
+
+ public double threegIdlePower(String oper) {
+ if(OPER_TMOBILE.equals(oper)) {
+ return 10;
+ }
+ return 10;
+ }
+
+ public double threegFachPower(String oper) {
+ if(OPER_TMOBILE.equals(oper)) {
+ return 401;
+ }
+ return 401;
+ }
+
+ public double threegDchPower(String oper) {
+ if(OPER_TMOBILE.equals(oper)) {
+ return 570;
+ }
+ return 570;
+ }
+
+ public int threegDchFachDelay(String oper) {
+ if(OPER_TMOBILE.equals(oper)) {
+ return 6;
+ } else if(OPER_ATT.equals(oper)) {
+ return 5;
+ }
+ return 4;
+ }
+
+ public int threegFachIdleDelay(String oper) {
+ if(OPER_TMOBILE.equals(oper)) {
+ return 4;
+ } else if(OPER_ATT.equals(oper)) {
+ return 12;
+ }
+ return 6;
+ }
+
+ public int threegUplinkQueue(String oper) {
+ return 151;
+ }
+
+ public int threegDownlinkQueue(String oper) {
+ return 119;
+ }
+
+ private double[] sensorPowerArray;
+ public double[] sensorPower() {
+ return sensorPowerArray;
+ }
+
+ public double getMaxPower(String componentName) {
+ if("LCD".equals(componentName)) {
+ return lcdBacklight() + lcdBrightness() * 255;
+ } else if("CPU".equals(componentName)) {
+ double[] ratios = cpuPowerRatios();
+ return ratios[ratios.length - 1] * 100;
+ } else if("Audio".equals(componentName)) {
+ return audioPower();
+ } else if("GPS".equals(componentName)) {
+ double[] gpsPow = gpsStatePower();
+ return gpsPow[gpsPow.length - 1];
+ } else if("Wifi".equals(componentName)) {
+ // TODO: Get a better estimation going here.
+ return 800;
+ } else if("3G".equals(componentName)) {
+ return threegDchPower("");
+ } else if("Sensors".equals(componentName)) {
+ double res = 0;
+ for(double x : sensorPower()) res += x;
+ return res;
+ } else {
+ return 900;
+ }
+ }
+}
diff --git a/src/edu/umich/PowerTutor/phone/DreamPowerCalculator.java b/src/edu/umich/PowerTutor/phone/DreamPowerCalculator.java
new file mode 100644
index 0000000..2248026
--- /dev/null
+++ b/src/edu/umich/PowerTutor/phone/DreamPowerCalculator.java
@@ -0,0 +1,163 @@
+/*
+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.phone;
+
+import edu.umich.PowerTutor.components.*;
+import edu.umich.PowerTutor.components.LCD.LcdData;
+import edu.umich.PowerTutor.components.OLED.OledData;
+import edu.umich.PowerTutor.components.CPU.CpuData;
+import edu.umich.PowerTutor.components.Audio.AudioData;
+import edu.umich.PowerTutor.components.GPS.GpsData;
+import edu.umich.PowerTutor.components.Wifi.WifiData;
+import edu.umich.PowerTutor.components.Threeg.ThreegData;
+import edu.umich.PowerTutor.components.Sensors.SensorData;
+
+import android.content.Context;
+
+public class DreamPowerCalculator implements PhonePowerCalculator {
+ protected PhoneConstants coeffs;
+
+ public DreamPowerCalculator(Context context) {
+ this(new DreamConstants(context));
+ }
+
+ protected DreamPowerCalculator(PhoneConstants coeffs) {
+ this.coeffs = coeffs;
+ }
+
+ public double getLcdPower(LcdData data) {
+ return data.screenOn ?
+ coeffs.lcdBrightness() * data.brightness + coeffs.lcdBacklight() : 0;
+ }
+
+ public double getOledPower(OledData data) {
+ throw new RuntimeException("getOledPower() should not be called for Dream");
+ }
+
+ public double getCpuPower(CpuData data) {
+ /* Find the two nearest cpu frequency and linearly interpolate
+ * the power ratio for that frequency.
+ */
+ double[] powerRatios = coeffs.cpuPowerRatios();
+ double[] freqs = coeffs.cpuFreqs();
+ double ratio;
+ if(powerRatios.length == 1) {
+ ratio = powerRatios[0];
+ } else {
+ double sfreq = data.freq;
+ if(sfreq < freqs[0]) sfreq = freqs[0];
+ if(sfreq > freqs[freqs.length - 1]) sfreq = freqs[freqs.length - 1];
+
+ int ind = upperBound(freqs, sfreq);
+ if(ind == 0) ind++;
+ if(ind == freqs.length) ind--;
+ ratio = powerRatios[ind - 1] + (powerRatios[ind] - powerRatios[ind - 1]) /
+ (freqs[ind] - freqs[ind - 1]) *
+ (sfreq - freqs[ind - 1]);
+ }
+ return Math.max(0, ratio * (data.usrPerc + data.sysPerc));
+ }
+
+ public double getAudioPower(AudioData data) {
+ return data.musicOn ? coeffs.audioPower() : 0;
+ }
+
+ public double getGpsPower(GpsData data) {
+ double result = 0;
+ double statePower[] = coeffs.gpsStatePower();
+ for(int i = 0; i < GPS.POWER_STATES; i++) {
+ result += data.stateTimes[i] * statePower[i];
+ }
+ return result;
+ }
+
+ public double getWifiPower(WifiData data) {
+ if(!data.wifiOn) {
+ return 0;
+ } else if(data.powerState == Wifi.POWER_STATE_LOW) {
+ return coeffs.wifiLowPower();
+ } else if(data.powerState == Wifi.POWER_STATE_HIGH) {
+ double[] linkSpeeds = coeffs.wifiLinkSpeeds();
+ double[] linkRatios = coeffs.wifiLinkRatios();
+ double ratio;
+ if(linkSpeeds.length == 1) {
+ /* If there is only one set speed we have to use its ratio as we have
+ * nothing else to go on.
+ */
+ ratio = linkRatios[0];
+ } else {
+ /* Find the two nearest speed/ratio pairs and linearly interpolate
+ * the ratio for this link speed.
+ */
+ int ind = upperBound(linkSpeeds, data.linkSpeed);
+ if(ind == 0) ind++;
+ if(ind == linkSpeeds.length) ind--;
+ ratio = linkRatios[ind - 1] + (linkRatios[ind] - linkRatios[ind - 1]) /
+ (linkSpeeds[ind] - linkSpeeds[ind - 1]) *
+ (data.linkSpeed - linkSpeeds[ind - 1]);
+ }
+ return Math.max(0, coeffs.wifiHighPower() + ratio * data.uplinkRate);
+ }
+ throw new RuntimeException("Unexpected power state");
+ }
+
+ public double getThreeGPower(ThreegData data) {
+ if(!data.threegOn) {
+ return 0;
+ } else {
+ switch(data.powerState) {
+ case Threeg.POWER_STATE_IDLE:
+ return coeffs.threegIdlePower(data.oper);
+ case Threeg.POWER_STATE_FACH:
+ return coeffs.threegFachPower(data.oper);
+ case Threeg.POWER_STATE_DCH:
+ return coeffs.threegDchPower(data.oper);
+ }
+ }
+ return 0;
+ }
+
+ public double getSensorPower(SensorData data) {
+ double result = 0;
+ double[] powerUse = coeffs.sensorPower();
+ for(int i = 0; i < Sensors.MAX_SENSORS; i++) {
+ result += data.onTime[i] * powerUse[i];
+ }
+ return result;
+ }
+
+ /* Returns the largest index y such that if x were inserted into A (which
+ * should already be sorted) at y then A would remain sorted.
+ */
+ protected static int upperBound(double[] A, double x) {
+ int lo = 0;
+ int hi = A.length;
+ while(lo < hi) {
+ int mid = lo + (hi - lo) / 2;
+ if(A[mid] <= x) {
+ lo = mid + 1;
+ } else {
+ hi = mid;
+ }
+ }
+ return lo;
+ }
+}
+
diff --git a/src/edu/umich/PowerTutor/phone/PassionConstants.java b/src/edu/umich/PowerTutor/phone/PassionConstants.java
new file mode 100644
index 0000000..c525bce
--- /dev/null
+++ b/src/edu/umich/PowerTutor/phone/PassionConstants.java
@@ -0,0 +1,160 @@
+/*
+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.phone;
+
+import android.content.Context;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+
+public class PassionConstants extends DreamConstants {
+ protected int screenWidth;
+ protected int screenHeight;
+
+ public PassionConstants(Context context) {
+ super(context);
+ DisplayMetrics metrics = new DisplayMetrics();
+ WindowManager windowManager =
+ (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ windowManager.getDefaultDisplay().getMetrics(metrics);
+ screenWidth = metrics.widthPixels;
+ screenHeight = metrics.heightPixels;
+ }
+
+ public String modelName() {
+ return "passion";
+ }
+
+ public double maxPower() {
+ return 2800;
+ }
+
+ public double lcdBrightness() {
+ return 1.2217;
+}
+
+ public double lcdBacklight() {
+ throw new RuntimeException("lcdBacklight() for passion which has no LCD " +
+ "display");
+ }
+
+ public double oledBasePower() {
+ return 365; // This incorporates all the constant component in the model.
+ }
+
+ private static final double[] arrayChannelPower = {
+ 3.0647e-006, 4.4799e-006, 6.4045e-006};
+ public double[] oledChannelPower() {
+ return arrayChannelPower;
+ }
+
+ public double oledModulation() {
+ return 1.7582e-006;
+ }
+
+ private static final double[] arrayCpuPowerRatios = {1.1273, 1.5907, 1.8736,
+ 2.1745, 2.6031, 2.9612, 3.1373,3.4513, 3.9073 ,4.1959, 4.6492, 5.4818};
+ public double[] cpuPowerRatios() {
+ return arrayCpuPowerRatios;
+ }
+
+ private static final double[] arrayCpuFreqs = {245, 384, 460, 499, 576, 614,
+ 653, 691, 768, 806, 845, 998};
+ public double[] cpuFreqs() {
+ return arrayCpuFreqs;
+ }
+
+ public double audioPower() {
+ return 106.81;
+ }
+
+ private static final double[] arrayGpsStatePower = {0.0, 17.5, 268.94};
+ public double[] gpsStatePower() {
+ return arrayGpsStatePower;
+ }
+
+ public double gpsSleepTime() {
+ return 6.0;
+ }
+
+ public double wifiLowPower() {
+ return 34.37;
+ }
+
+ public double wifiHighPower() {
+ return 404.46 ;
+ }
+
+ public double wifiLowHighTransition() {
+ return 15;
+ }
+
+ public double wifiHighLowTransition() {
+ return 8;
+ }
+
+ private static final double[] arrayWifiLinkRatios = {
+ 47.122645, 46.354821, 43.667437, 43.283525, 40.980053, 39.44422, 38.676581,
+ 34.069637, 29.462693, 20.248805, 11.034917, 6.427122
+ };
+ public double[] wifiLinkRatios() {
+ return arrayWifiLinkRatios;
+ }
+
+ private static final double[] arrayWifiLinkSpeeds = {
+ 1, 2, 5.5, 6, 9, 11, 12, 18, 24, 36, 48, 54
+ };
+ public double[] wifiLinkSpeeds() {
+ return arrayWifiLinkSpeeds;
+ }
+
+ public String threegInterface() {
+ return "rmnet0";
+ }
+
+ public double threegIdlePower(String oper) {
+ if(OPER_TMOBILE.equals(oper)) {
+ return 10;
+ }
+ return 10; // Return the worst case for unknown operators.
+ }
+
+ public double threegFachPower(String oper) {
+ if(OPER_TMOBILE.equals(oper)) {
+ return 405.81;
+ }
+ return 405.81; // Return the worst case for unknown operators.
+ }
+
+ public double threegDchPower(String oper) {
+ if(OPER_TMOBILE.equals(oper)) {
+ return 902.03;
+ }
+ return 902.03; // Return the worst case for unknown operators.
+ }
+
+ public double getMaxPower(String componentName) {
+ if("OLED".equals(componentName)) {
+ double[] channel = oledChannelPower();
+ return oledBasePower() + 255 * screenWidth * screenHeight *
+ (channel[0] + channel[1] + channel[2] - oledModulation());
+ }
+ return super.getMaxPower(componentName);
+ }
+}
diff --git a/src/edu/umich/PowerTutor/phone/PassionPowerCalculator.java b/src/edu/umich/PowerTutor/phone/PassionPowerCalculator.java
new file mode 100644
index 0000000..3ab78b2
--- /dev/null
+++ b/src/edu/umich/PowerTutor/phone/PassionPowerCalculator.java
@@ -0,0 +1,56 @@
+/*
+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.phone;
+
+import edu.umich.PowerTutor.components.LCD.LcdData;
+import edu.umich.PowerTutor.components.OLED.OledData;
+import edu.umich.PowerTutor.components.CPU.CpuData;
+import edu.umich.PowerTutor.components.Audio.AudioData;
+import edu.umich.PowerTutor.components.GPS.GpsData;
+import edu.umich.PowerTutor.components.Wifi.WifiData;
+import edu.umich.PowerTutor.components.Threeg.ThreegData;
+
+import android.content.Context;
+
+/* Most of this file should be inheritted from DreamPowerCalculator as most of
+ * the hardware model details will be the same modulo the coefficients.
+ */
+public class PassionPowerCalculator extends DreamPowerCalculator {
+ public PassionPowerCalculator(Context context) {
+ super(new PassionConstants(context));
+ }
+
+ public PassionPowerCalculator(PhoneConstants coeffs) {
+ super(coeffs);
+ }
+
+ @Override
+ public double getOledPower(OledData data) {
+ if(!data.screenOn) {
+ return 0;
+ }
+ if(data.pixPower == -1) {
+ /* No pixel power available :(. */
+ return coeffs.oledBasePower() + coeffs.lcdBrightness() * data.brightness;
+ } else {
+ return coeffs.oledBasePower() + data.pixPower * data.brightness;
+ }
+ }
+}
diff --git a/src/edu/umich/PowerTutor/phone/PhoneConstants.java b/src/edu/umich/PowerTutor/phone/PhoneConstants.java
new file mode 100644
index 0000000..73b1950
--- /dev/null
+++ b/src/edu/umich/PowerTutor/phone/PhoneConstants.java
@@ -0,0 +1,150 @@
+/*
+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.phone;
+
+public interface PhoneConstants {
+ /* Return the name of the model represented by these constants
+ */
+ public String modelName();
+
+ /* Gives the maximum power that this phone can drain from its battery.
+ */
+ public double maxPower();
+
+ /* Gives the coefficient to multiply the LCD display's brightness by to
+ * calculate power usage.
+ */
+ public double lcdBrightness();
+
+ /* Gives the power usage for the lcd display that is incurred by the display
+ * just being turned on.
+ */
+ public double lcdBacklight();
+
+ /* Gives the base power usage for the OLED display for just being on.
+ */
+ public double oledBasePower();
+
+ /* Gives the power coefficient for rgb channels (in that order) for a signle
+ * pixel.
+ */
+ public double[] oledChannelPower();
+
+ /* Gives the modulation coefficient for the per pixel power calculation.
+ */
+ public double oledModulation();
+
+ /* Gives the coefficients at different cpu frequencies for the amount of
+ * power/cpu utilization the processor is using.
+ */
+ public double[] cpuPowerRatios();
+
+ /* Gives the frequency for each of the power ratios listed in
+ * cpuPowerRatios().
+ */
+ public double[] cpuFreqs();
+
+ /* Gives the usage for the audio output being used. The model doesn't
+ * currently take into account volume.
+ */
+ public double audioPower();
+
+ /* Gives the power consumption for each of the GPS states. These states are
+ * {OFF, SLEEP, ON} in that order. See GPS.java.
+ */
+ public double[] gpsStatePower();
+
+ /* Gives the time in seconds that the GPS sleeps for after the session
+ * has ended.
+ */
+ public double gpsSleepTime();
+
+ /* Gives the power consumption of wifi in the low power state.
+ */
+ public double wifiLowPower();
+
+ /* Gives the base power consumption while the wifi is in high power mode.
+ */
+ public double wifiHighPower();
+
+ /* Gives the packet rate needed to transition from the low power state
+ * to the high power state.
+ */
+ public double wifiLowHighTransition();
+
+ /* Gives the packet rate needed to transition from the high power state
+ * to the low power state.
+ */
+ public double wifiHighLowTransition();
+
+ /* Gives the power/uplinkrate for different link speeds for wifi in high
+ * power mode.
+ */
+ public double[] wifiLinkRatios();
+
+ /* Gives the link speed associated with each link power ratio. Elements
+ * should be in increasing order. Should have the same number of elements
+ * as wifiLinkRatios().
+ */
+ public double[] wifiLinkSpeeds();
+
+ /* Gives the name of the 3G interface for this phone.
+ */
+ public String threegInterface();
+
+ /* Gives the power consumed while the 3G interface is in the idle state.
+ */
+ public double threegIdlePower(String oper);
+
+ /* Gives the power consumed while the 3G interface is in the FACH state.
+ */
+ public double threegFachPower(String oper);
+
+ /* Gives the power consumed while the 3G interface is in the DCH state.
+ */
+ public double threegDchPower(String oper);
+
+ /* Gives the number of bytes in the uplink queue.
+ */
+ public int threegUplinkQueue(String oper);
+
+ /* Gives the number of bytes in the downlink queue.
+ */
+ public int threegDownlinkQueue(String oper);
+
+ /* Gives the time in seconds that the 3G interface stays idle in the DCH state
+ * before transitioning to the FACH state.
+ */
+ public int threegDchFachDelay(String oper);
+
+ /* Gives the time in seconds that the 3G interface stays idle in the FACH
+ * state before transitioning to the IDLE state.
+ */
+ public int threegFachIdleDelay(String oper);
+
+ /* Gives the power consumed by each of the sensors. Should have the same size
+ * as Sensors.MAX_SENSORS.
+ */
+ public double[] sensorPower();
+
+ /* Gives the maximum power in mW that the named component can generate.
+ */
+ public double getMaxPower(String componentName);
+}
diff --git a/src/edu/umich/PowerTutor/phone/PhonePowerCalculator.java b/src/edu/umich/PowerTutor/phone/PhonePowerCalculator.java
new file mode 100644
index 0000000..addcfe1
--- /dev/null
+++ b/src/edu/umich/PowerTutor/phone/PhonePowerCalculator.java
@@ -0,0 +1,48 @@
+/*
+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.phone;
+
+import edu.umich.PowerTutor.components.LCD.LcdData;
+import edu.umich.PowerTutor.components.OLED.OledData;
+import edu.umich.PowerTutor.components.CPU.CpuData;
+import edu.umich.PowerTutor.components.Audio.AudioData;
+import edu.umich.PowerTutor.components.GPS.GpsData;
+import edu.umich.PowerTutor.components.Wifi.WifiData;
+import edu.umich.PowerTutor.components.Threeg.ThreegData;
+import edu.umich.PowerTutor.components.Sensors.SensorData;
+
+public interface PhonePowerCalculator {
+ public double getLcdPower(LcdData data);
+
+ public double getOledPower(OledData data);
+
+ public double getCpuPower(CpuData data);
+
+ public double getAudioPower(AudioData data);
+
+ public double getGpsPower(GpsData data);
+
+ public double getWifiPower(WifiData data);
+
+ public double getThreeGPower(ThreegData data);
+
+ public double getSensorPower(SensorData data);
+}
+
diff --git a/src/edu/umich/PowerTutor/phone/PhoneSelector.java b/src/edu/umich/PowerTutor/phone/PhoneSelector.java
new file mode 100644
index 0000000..54ebd43
--- /dev/null
+++ b/src/edu/umich/PowerTutor/phone/PhoneSelector.java
@@ -0,0 +1,204 @@
+/*
+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.phone;
+
+import java.util.List;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.Log;
+import edu.umich.PowerTutor.components.Audio;
+import edu.umich.PowerTutor.components.CPU;
+import edu.umich.PowerTutor.components.GPS;
+import edu.umich.PowerTutor.components.LCD;
+import edu.umich.PowerTutor.components.OLED;
+import edu.umich.PowerTutor.components.PowerComponent;
+import edu.umich.PowerTutor.components.Sensors;
+import edu.umich.PowerTutor.components.Threeg;
+import edu.umich.PowerTutor.components.Wifi;
+import edu.umich.PowerTutor.components.Audio.AudioData;
+import edu.umich.PowerTutor.components.CPU.CpuData;
+import edu.umich.PowerTutor.components.GPS.GpsData;
+import edu.umich.PowerTutor.components.LCD.LcdData;
+import edu.umich.PowerTutor.components.OLED.OledData;
+import edu.umich.PowerTutor.components.Sensors.SensorData;
+import edu.umich.PowerTutor.components.Threeg.ThreegData;
+import edu.umich.PowerTutor.components.Wifi.WifiData;
+import edu.umich.PowerTutor.service.PowerData;
+import edu.umich.PowerTutor.util.NotificationService;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+public class PhoneSelector {
+ private static final String TAG = "PhoneSelector";
+
+ public static final int PHONE_UNKNOWN = 0;
+ public static final int PHONE_DREAM = 1; /* G1 */
+ public static final int PHONE_SAPPHIRE = 2; /* G2 */
+ public static final int PHONE_PASSION = 3; /* Nexus One */
+
+ /* A hard-coded list of phones that have OLED screens. */
+ public static final String[] OLED_PHONES = {
+ "bravo",
+ "passion",
+ "GT-I9000",
+ "inc",
+ "legend",
+ "GT-I7500",
+ "SPH-M900",
+ "SGH-I897",
+ "SGH-T959",
+ "desirec",
+ };
+
+
+ /* This class is not supposed to be instantiated. Just use the static
+ * members.
+ */
+ private PhoneSelector() {}
+
+ public static boolean phoneSupported() {
+ return getPhoneType() != PHONE_UNKNOWN;
+ }
+
+ public static boolean hasOled() {
+ for(int i = 0; i < OLED_PHONES.length; i++) {
+ if(Build.DEVICE.equals(OLED_PHONES[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static int getPhoneType() {
+ if(Build.DEVICE.startsWith("dream")) return PHONE_DREAM;
+ if(Build.DEVICE.startsWith("sapphire")) return PHONE_SAPPHIRE;
+ if(Build.DEVICE.startsWith("passion")) return PHONE_PASSION;
+ return PHONE_UNKNOWN;
+ }
+
+ public static PhoneConstants getConstants(Context context) {
+ switch(getPhoneType()) {
+ case PHONE_DREAM:
+ return new DreamConstants(context);
+ case PHONE_SAPPHIRE:
+ return new SapphireConstants(context);
+ case PHONE_PASSION:
+ return new PassionConstants(context);
+ default:
+ boolean oled = hasOled();
+ Log.w(TAG, "Phone type not recognized (" + Build.DEVICE + "), using " +
+ (oled ? "Passion" : "Dream") + " constants");
+ return oled ? new PassionConstants(context) :
+ new DreamConstants(context);
+ }
+ }
+
+ public static PhonePowerCalculator getCalculator(Context context) {
+ switch(getPhoneType()) {
+ case PHONE_DREAM:
+ return new DreamPowerCalculator(context);
+ case PHONE_SAPPHIRE:
+ return new SapphirePowerCalculator(context);
+ case PHONE_PASSION:
+ return new PassionPowerCalculator(context);
+ default:
+ boolean oled = hasOled();
+ Log.w(TAG, "Phone type not recognized (" + Build.DEVICE + "), using " +
+ (oled ? "Passion" : "Dream") + " calculator");
+ return oled ? new PassionPowerCalculator(context) :
+ new DreamPowerCalculator(context);
+ }
+ }
+
+ public static void generateComponents(Context context,
+ List<PowerComponent> components,
+ List<PowerFunction> functions) {
+ final PhoneConstants constants = getConstants(context);
+ final PhonePowerCalculator calculator = getCalculator(context);
+
+ //TODO: What about bluetooth?
+ //TODO: LED light on the Nexus
+
+ /* Add display component. */
+ if(hasOled()) {
+ components.add(new OLED(context, constants));
+ functions.add(new PowerFunction() {
+ public double calculate(PowerData data) {
+ return calculator.getOledPower((OledData)data);
+ }});
+ } else {
+ components.add(new LCD(context));
+ functions.add(new PowerFunction() {
+ public double calculate(PowerData data) {
+ return calculator.getLcdPower((LcdData)data);
+ }});
+ }
+
+ /* Add CPU component. */
+ components.add(new CPU(constants));
+ functions.add(new PowerFunction() {
+ public double calculate(PowerData data) {
+ return calculator.getCpuPower((CpuData)data);
+ }});
+
+ /* Add Wifi component. */
+ String wifiInterface =
+ SystemInfo.getInstance().getProperty("wifi.interface");
+ if(wifiInterface != null && wifiInterface.length() != 0) {
+ components.add(new Wifi(context, constants));
+ functions.add(new PowerFunction() {
+ public double calculate(PowerData data) {
+ return calculator.getWifiPower((WifiData)data);
+ }});
+ }
+
+ /* Add 3G component. */
+ if(constants.threegInterface().length() != 0) {
+ components.add(new Threeg(context, constants));
+ functions.add(new PowerFunction() {
+ public double calculate(PowerData data) {
+ return calculator.getThreeGPower((ThreegData)data);
+ }});
+ }
+
+ /* Add GPS component. */
+ components.add(new GPS(context, constants));
+ functions.add(new PowerFunction() {
+ public double calculate(PowerData data) {
+ return calculator.getGpsPower((GpsData)data);
+ }});
+
+ /* Add Audio component. */
+ components.add(new Audio(context));
+ functions.add(new PowerFunction() {
+ public double calculate(PowerData data) {
+ return calculator.getAudioPower((AudioData)data);
+ }});
+
+ /* Add Sensors component if avaialble. */
+ if(NotificationService.available()) {
+ components.add(new Sensors(context));
+ functions.add(new PowerFunction() {
+ public double calculate(PowerData data) {
+ return calculator.getSensorPower((SensorData)data);
+ }});
+ }
+ }
+}
diff --git a/src/edu/umich/PowerTutor/phone/PowerFunction.java b/src/edu/umich/PowerTutor/phone/PowerFunction.java
new file mode 100644
index 0000000..d32e467
--- /dev/null
+++ b/src/edu/umich/PowerTutor/phone/PowerFunction.java
@@ -0,0 +1,26 @@
+/*
+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.phone;
+
+import edu.umich.PowerTutor.service.PowerData;
+
+public interface PowerFunction {
+ public double calculate(PowerData data);
+}
diff --git a/src/edu/umich/PowerTutor/phone/SapphireConstants.java b/src/edu/umich/PowerTutor/phone/SapphireConstants.java
new file mode 100644
index 0000000..c0045cd
--- /dev/null
+++ b/src/edu/umich/PowerTutor/phone/SapphireConstants.java
@@ -0,0 +1,123 @@
+/*
+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.phone;
+
+import android.content.Context;
+
+public class SapphireConstants extends DreamConstants {
+ public SapphireConstants(Context context) {
+ super(context);
+ }
+
+ public String modelName() {
+ return "sapphire";
+ }
+
+ public double maxPower() {
+ return 2800;
+ }
+
+ public double lcdBrightness() {
+ return 1.72686;
+ }
+
+ public double lcdBacklight() {
+ return 340.8305;
+ }
+
+ private static final double[] arrayCpuPowerRatios = {1.4169, 2.3997};
+ public double[] cpuPowerRatios() {
+ return arrayCpuPowerRatios;
+ }
+
+ private static final double[] arrayCpuFreqs = {245.36, 383.38};
+ public double[] cpuFreqs() {
+ return arrayCpuFreqs;
+ }
+
+ public double audioPower() {
+ return 184.62;
+ }
+
+ private static final double[] arrayGpsStatePower = {0.0, 33.577, 284.7624};
+ public double[] gpsStatePower() {
+ return arrayGpsStatePower;
+ }
+
+ public double gpsSleepTime() {
+ return 6.0;
+ }
+
+ public double wifiLowPower() {
+ return 38.554;
+ }
+
+ public double wifiHighPower() {
+ return 733.7631;
+ }
+
+ public double wifiLowHighTransition() {
+ return 15;
+ }
+
+ public double wifiHighLowTransition() {
+ return 8;
+ }
+
+ private static final double[] arrayWifiLinkRatios = {
+ 47.122645, 46.354821, 43.667437, 43.283525, 40.980053, 39.44422, 38.676581,
+ 34.069637, 29.462693, 20.248805, 11.034917, 6.427122
+ };
+ public double[] wifiLinkRatios() {
+ return arrayWifiLinkRatios;
+ }
+
+ private static final double[] arrayWifiLinkSpeeds = {
+ 1, 2, 5.5, 6, 9, 11, 12, 18, 24, 36, 48, 54
+ };
+ public double[] wifiLinkSpeeds() {
+ return arrayWifiLinkSpeeds;
+ }
+
+ public String threegInterface() {
+ return "rmnet0";
+ }
+
+ public double threegIdlePower(String oper) {
+ if(OPER_TMOBILE.equals(oper)) {
+ return 10;
+ }
+ return 10;
+ }
+
+ public double threegFachPower(String oper) {
+ if(OPER_TMOBILE.equals(oper)) {
+ return 413.9689;
+ }
+ return 413.9689;
+ }
+
+ public double threegDchPower(String oper) {
+ if(OPER_TMOBILE.equals(oper)) {
+ return 944.3891;
+ }
+ return 944.3891;
+ }
+}
diff --git a/src/edu/umich/PowerTutor/phone/SapphirePowerCalculator.java b/src/edu/umich/PowerTutor/phone/SapphirePowerCalculator.java
new file mode 100644
index 0000000..b390ce1
--- /dev/null
+++ b/src/edu/umich/PowerTutor/phone/SapphirePowerCalculator.java
@@ -0,0 +1,42 @@
+/*
+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.phone;
+
+import edu.umich.PowerTutor.components.LCD.LcdData;
+import edu.umich.PowerTutor.components.CPU.CpuData;
+import edu.umich.PowerTutor.components.Audio.AudioData;
+import edu.umich.PowerTutor.components.GPS.GpsData;
+import edu.umich.PowerTutor.components.Wifi.WifiData;
+import edu.umich.PowerTutor.components.Threeg.ThreegData;
+
+import android.content.Context;
+
+/* Most of this file should be inheritted from DreamPowerCalculator as most of
+ * the hardware model details will be the same modulo the coefficients.
+ */
+public class SapphirePowerCalculator extends DreamPowerCalculator {
+ public SapphirePowerCalculator(Context context) {
+ super(new SapphireConstants(context));
+ }
+
+ public SapphirePowerCalculator(PhoneConstants coeffs) {
+ super(coeffs);
+ }
+}
diff --git a/src/edu/umich/PowerTutor/service/ICounterService.aidl b/src/edu/umich/PowerTutor/service/ICounterService.aidl
new file mode 100644
index 0000000..96e5b38
--- /dev/null
+++ b/src/edu/umich/PowerTutor/service/ICounterService.aidl
@@ -0,0 +1,79 @@
+/*
+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;
+
+interface ICounterService {
+ // Returns the name of the components that are being logged.
+ String[] getComponents();
+
+ // Returns the maximum power usage for each of the components being logged.
+ int[] getComponentsMaxPower();
+
+ // Returns a bit mask with a 1 in the ith bit if component i doesn't have
+ // uid specific information.
+ int getNoUidMask();
+
+ // Returns the power consumption in mW for component componentId for the last
+ // count iterations. uid can be specified to make this data only include a
+ // specific user id or you can provide SystemInfo.AID_ALL to request
+ // global power state information.
+ int[] getComponentHistory(int count, int componentId, int uid);
+
+ // Returns the total energy consumption for each component in the same order
+ // that the components were returned in getComponents() and in the same order
+ // that the components are populated by PhoneSelector.generateComponents().
+ //
+ // uid can be specified to make the information specific to a single user
+ // id or SystemInfo.AID_ALL can be specified to request global information.
+ //
+ // windowType should be one of Counter.WINDOW_MINUTE, Counter.WINDOW_HOUR,
+ // Counter.WINDOW_DAY, Counter.WINDOW_TOTAL to request the window that the
+ // energy usage will be calculated over.
+ //
+ // The returned result is given in mJ.
+ long[] getTotals(int uid, int windowType);
+
+ // Like getTotals() except that each entry is divided by how long the given
+ // uid was running. If SystemInfo.AID_ALL is provided this is effectively
+ // like dividing each entry by the window size. (unless PowerTutor hasn't
+ // been running that long).
+ long[] getMeans(int uid, int windowType);
+
+ // Gets the total time that this uid has been running in seconds.
+ long getRuntime(int uid, int windowType);
+
+ // Returns a byte array representing a serialized array of UidInfo structures.
+ // See UidInfo.java for what information is given. Note that members marked
+ // as transient are not filled in.
+ // Power contributions from component i will be dropped if the ith bit is set
+ // in ignoreMask. Providing 0 for ignoreMask will give results for all
+ // components.
+ //
+ // Example Usage:
+ // byte[] rawUidInfo = counterService.getUidInfo(windowType);
+ // UidInfo[] uidInfos = (UidInfo[])new ObjectInputStream(
+ // new ByteArrayInputStream(rawUidInfo)).readObject();
+ byte[] getUidInfo(int windowType, int ignoreMask);
+
+ // Return miscellaneous data point for the passed uid.
+ // Current extras included:
+ // OLEDSCORE
+ long getUidExtra(String name, int uid);
+}
diff --git a/src/edu/umich/PowerTutor/service/IterationData.java b/src/edu/umich/PowerTutor/service/IterationData.java
new file mode 100644
index 0000000..db75bb9
--- /dev/null
+++ b/src/edu/umich/PowerTutor/service/IterationData.java
@@ -0,0 +1,76 @@
+/*
+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.util.Recycler;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+import android.util.SparseArray;
+
+/* Class that encloses physical hardware power data as well as estimated power
+ * data for each uid that contributes a non-negligable amount of power for this
+ * component.
+ */
+public class IterationData {
+ private static Recycler<IterationData> recycler =
+ new Recycler<IterationData>();
+
+ private SparseArray<PowerData> uidPower;
+
+ public static IterationData obtain() {
+ IterationData result = recycler.obtain();
+ if(result != null) return result;
+ return new IterationData();
+ }
+
+ private IterationData() {
+ uidPower = new SparseArray<PowerData>();
+ }
+
+ /* Initialize the members of this structure. Remember that this class may not
+ * have just been instantiated and may have been used in past iterations.
+ */
+ public void init() {
+ uidPower.clear();
+ }
+
+ /* Allow this class to be recycled and to recycle all of the PowerData
+ * PowerData elements contained within it.
+ */
+ public void recycle() {
+ for(int i = 0; i < uidPower.size(); i++) {
+ uidPower.valueAt(i).recycle();
+ }
+ uidPower.clear();
+ recycler.recycle(this);
+ }
+
+ public void setPowerData(PowerData power) {
+ addUidPowerData(SystemInfo.AID_ALL, power);
+ }
+
+ public void addUidPowerData(int uid, PowerData power) {
+ uidPower.put(uid, power);
+ }
+
+ public SparseArray<PowerData> getUidPowerData() {
+ return uidPower;
+ }
+}
diff --git a/src/edu/umich/PowerTutor/service/LogUploader.java b/src/edu/umich/PowerTutor/service/LogUploader.java
new file mode 100644
index 0000000..b07f542
--- /dev/null
+++ b/src/edu/umich/PowerTutor/service/LogUploader.java
@@ -0,0 +1,68 @@
+/*
+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 android.content.Context;
+
+/* This class is responsible for implementing all policy decisions on when to
+ * send log information back to an external server. Upon a successful call to
+ * shouldUpload() PowerEstimator will then call upload(file). After the call to
+ * upload the file with the passed path may be overwritten.
+ *
+ * This is a stub implementation not supporting log uploading.
+ */
+public class LogUploader {
+ public LogUploader(Context context) {
+ }
+
+ /* Returns true if this module supports uploading logs. */
+ public static boolean uploadSupported() {
+ return false;
+ }
+
+ /* Returns true if the log should be uploaded now. This may depend on log
+ * file size, network conditions, etc. */
+ // TODO: This should probably give the file name of the log
+ public boolean shouldUpload() {
+ return false;
+ }
+
+ /* Called when the device is plugged in or unplugged. The intended use of
+ * this is to improve upload policy decisions. */
+ public void plug(boolean plugged) {
+ }
+
+ /* Initiate the upload of the file with the passed location. */
+ public void upload(String origFile) {
+ }
+
+ /* Returns true if a file is currently being uploaded. */
+ public boolean isUploading() {
+ return false;
+ }
+
+ /* Interrupt any threads doing upload work. */
+ public void interrupt() {
+ }
+
+ /* Join any threads that may be performing log upload work. */
+ public void join() throws InterruptedException {
+ }
+}
diff --git a/src/edu/umich/PowerTutor/service/PowerData.java b/src/edu/umich/PowerTutor/service/PowerData.java
new file mode 100644
index 0000000..2033518
--- /dev/null
+++ b/src/edu/umich/PowerTutor/service/PowerData.java
@@ -0,0 +1,48 @@
+/*
+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 java.io.OutputStreamWriter;
+import java.io.IOException;
+import java.util.Vector;
+
+public abstract class PowerData {
+ private int cachedPower;
+
+ public PowerData() {
+ }
+
+ public void setCachedPower(int power) {
+ cachedPower = power;
+ }
+
+ public int getCachedPower() {
+ return cachedPower;
+ }
+
+ /* To be called when the PowerData object is no longer in use so that it can
+ * be used again in the next iteration if it chooses to be.
+ */
+ public void recycle() {}
+
+ /* Simply writes out log information to the passed stream. */
+ public abstract void writeLogDataInfo(OutputStreamWriter out)
+ throws IOException;
+}
diff --git a/src/edu/umich/PowerTutor/service/PowerEstimator.java b/src/edu/umich/PowerTutor/service/PowerEstimator.java
new file mode 100644
index 0000000..93e5327
--- /dev/null
+++ b/src/edu/umich/PowerTutor/service/PowerEstimator.java
@@ -0,0 +1,580 @@
+/*
+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.components.PowerComponent;
+import edu.umich.PowerTutor.components.OLED;
+import edu.umich.PowerTutor.phone.PhoneSelector;
+import edu.umich.PowerTutor.phone.PhoneConstants;
+import edu.umich.PowerTutor.phone.PowerFunction;
+import edu.umich.PowerTutor.util.BatteryStats;
+import edu.umich.PowerTutor.util.Counter;
+import edu.umich.PowerTutor.util.HistoryBuffer;
+import edu.umich.PowerTutor.util.NotificationService;
+import edu.umich.PowerTutor.util.SystemInfo;
+import edu.umich.PowerTutor.widget.PowerWidget;
+
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.SystemClock;
+import android.preference.PreferenceManager;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.text.DecimalFormat;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+/** This class is responsible for starting the individual power component
+ * loggers (CPU, GPS, etc...) and collecting the information they generate.
+ * This information is used both to write a log file that will be send back
+ * to spidermoneky (or looked at by the user) and to implement the
+ * ICounterService IPC interface.
+ */
+public class PowerEstimator implements Runnable {
+ private static final String TAG = "PowerEstimator";
+
+ /* A dictionary used to assist in compression of the log files. Strings that
+ * appear more frequently should be put towards the end of the dictionary. It
+ * is not critical that every string that be written to the log appear here.
+ */
+ private static final String DEFLATE_DICTIONARY =
+ "onoffidleoff-hookringinglowairplane-modebatteryedgeGPRS3Gunknown" +
+ "in-serviceemergency-onlyout-of-servicepower-offdisconnectedconnecting" +
+ "associateconnectedsuspendedphone-callservicenetworkbegin.0123456789" +
+ "GPSAudioWifi3GLCDCPU-power ";
+
+ public static final int ALL_COMPONENTS = -1;
+ public static final int ITERATION_INTERVAL = 1000; // 1 second
+
+ private UMLoggerService context;
+ private SharedPreferences prefs;
+ private boolean plugged;
+
+ private Vector<PowerComponent> powerComponents;
+ private Vector<PowerFunction> powerFunctions;
+ private Vector<HistoryBuffer> histories;
+ private Map<Integer, String> uidAppIds;
+
+ // Miscellaneous data.
+ private HistoryBuffer oledScoreHistory;
+
+ private Object fileWriteLock = new Object();
+ private LogUploader logUploader;
+ private OutputStreamWriter logStream;
+ private DeflaterOutputStream deflateStream;
+
+ private Object iterationLock = new Object();
+ private long lastWrittenIteration;
+
+ public PowerEstimator(UMLoggerService context){
+ this.context = context;
+ prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ powerComponents = new Vector<PowerComponent>();
+ powerFunctions = new Vector<PowerFunction>();
+ uidAppIds = new HashMap<Integer, String>();
+ PhoneSelector.generateComponents(context, powerComponents, powerFunctions);
+
+ histories = new Vector<HistoryBuffer>();
+ for(int i = 0; i < powerComponents.size(); i++) {
+ histories.add(new HistoryBuffer(300));
+ }
+ oledScoreHistory = new HistoryBuffer(0);
+
+ logUploader = new LogUploader(context);
+ openLog(true);
+ }
+
+ private void openLog(boolean init) {
+ /* Open up the log file if possible. */
+ try {
+ String logFilename = context.getFileStreamPath(
+ "PowerTrace.log").getAbsolutePath();
+ if(init && prefs.getBoolean("sendPermission", true) &&
+ new File(logFilename).length() > 0) {
+ /* There is data to send. Make sure that gets going in the sending
+ * process before we write over any old logs.
+ */
+ logUploader.upload(logFilename);
+ }
+ Deflater deflater = new Deflater();
+ deflater.setDictionary(DEFLATE_DICTIONARY.getBytes());
+ deflateStream = new DeflaterOutputStream(
+ new FileOutputStream(logFilename));
+ logStream = new OutputStreamWriter(deflateStream);
+ } catch(IOException e) {
+ logStream = null;
+ Log.e(TAG, "Failed to open log file. No log will be kept.");
+ }
+ }
+
+ /** This is the loop that keeps updating the power profile
+ */
+ public void run() {
+ SystemInfo sysInfo = SystemInfo.getInstance();
+ PackageManager pm = context.getPackageManager();
+ BatteryStats bst = BatteryStats.getInstance();
+
+ int components = powerComponents.size();
+ long beginTime = SystemClock.elapsedRealtime();
+ for(int i = 0; i < components; i++) {
+ powerComponents.get(i).init(beginTime, ITERATION_INTERVAL);
+ powerComponents.get(i).start();
+ }
+ IterationData[] dataTemp = new IterationData[components];
+
+ PhoneConstants phoneConstants = PhoneSelector.getConstants(context);
+ long[] memInfo = new long[4];
+
+ int oledId = -1;
+ for(int i = 0; i < components; i++) {
+ if("OLED".equals(powerComponents.get(i).getComponentName())) {
+ oledId = i;
+ break;
+ }
+ }
+
+ /* Indefinitely collect data on each of the power components. */
+ boolean firstLogIteration = true;
+ for(long iter = -1; !Thread.interrupted(); ) {
+ long curTime = SystemClock.elapsedRealtime();
+ /* Compute the next iteration that we can make the ending of. We wait
+ for the end of the iteration so that the components had a chance to
+ collect data already.
+ */
+ iter = (long)Math.max(iter + 1,
+ (curTime - beginTime) / ITERATION_INTERVAL);
+ /* Sleep until the next iteration completes. */
+ try {
+ Thread.currentThread().sleep(
+ beginTime + (iter + 1) * ITERATION_INTERVAL - curTime);
+ } catch(InterruptedException e) {
+ break;
+ }
+
+ int totalPower = 0;
+ for(int i = 0; i < components; i++) {
+ PowerComponent comp = powerComponents.get(i);
+ IterationData data = comp.getData(iter);
+ dataTemp[i] = data;
+ if(data == null) {
+ /* No data present for this timestamp. No power charged.
+ */
+ continue;
+ }
+
+ SparseArray<PowerData> uidPower = data.getUidPowerData();
+ for(int j = 0; j < uidPower.size(); j++) {
+ int uid = uidPower.keyAt(j);
+ PowerData powerData = uidPower.valueAt(j);
+ int power = (int)powerFunctions.get(i).calculate(powerData);
+ powerData.setCachedPower(power);
+ histories.get(i).add(uid, iter, power);
+ if(uid == SystemInfo.AID_ALL) {
+ totalPower += power;
+ }
+ if(i == oledId) {
+ OLED.OledData oledData = (OLED.OledData)powerData;
+ if(oledData.pixPower >= 0) {
+ oledScoreHistory.add(uid, iter, (int)(1000 * oledData.pixPower));
+ }
+ }
+ }
+ }
+
+ /* Update the uid set. */
+ synchronized(fileWriteLock) { synchronized(uidAppIds) {
+ for(int i = 0; i < components; i++) {
+ IterationData data = dataTemp[i];
+ if(data == null) {
+ continue;
+ }
+ SparseArray<PowerData> uidPower = data.getUidPowerData();
+ for(int j = 0; j < uidPower.size(); j++) {
+ int uid = uidPower.keyAt(j);
+ if(uid < SystemInfo.AID_APP) {
+ uidAppIds.put(uid, null);
+ } else {
+ /* We only want to update app names when logging so the associcate
+ * message gets written.
+ */
+ String appId = uidAppIds.get(uid);
+ String newAppId = sysInfo.getAppId(uid, pm);
+ if(!firstLogIteration && logStream != null &&
+ (appId == null || !appId.equals(newAppId))) {
+ try {
+ logStream.write("associate " + uid + " " + newAppId + "\n");
+ } catch(IOException e) {
+ Log.w(TAG, "Failed to write to log file");
+ }
+ }
+ uidAppIds.put(uid, newAppId);
+ }
+ }
+ }
+ }}
+
+ synchronized(iterationLock) {
+ lastWrittenIteration = iter;
+ }
+
+ /* Update the icon display every 15 iterations. */
+ if(iter % 15 == 14) {
+ final double POLY_WEIGHT = 0.02;
+ int count = 0;
+ int[] history = getComponentHistory(5 * 60, -1,
+ SystemInfo.AID_ALL, -1);
+ double weightedAvgPower = 0;
+ for(int i = history.length - 1; i >= 0; i--) {
+ if(history[i] != 0) {
+ count++;
+ weightedAvgPower *= 1.0 - POLY_WEIGHT;
+ weightedAvgPower += POLY_WEIGHT * history[i] / 1000.0;
+ }
+ }
+ double avgPower = -1;
+ if(count != 0) {
+ avgPower = weightedAvgPower /
+ (1.0 - Math.pow(1.0 - POLY_WEIGHT, count));
+ }
+ avgPower *= 1000;
+
+ context.updateNotification((int)Math.min(8, 1 +
+ 8 * avgPower / phoneConstants.maxPower()),
+ avgPower);
+ }
+
+ /* Update the widget. */
+ if(iter % 60 == 0) {
+ PowerWidget.updateWidget(context, this);
+ }
+
+ if(iter % (5*60) == 0) {
+ if(bst.hasCurrent()) {
+ writeToLog("batt_current " + bst.getCurrent() + "\n");
+ }
+ if(bst.hasTemp()) {
+ writeToLog("batt_temp " + bst.getTemp() + "\n");
+ }
+ if(bst.hasCharge()) {
+ writeToLog("batt_charge " + bst.getCharge() + "\n");
+ }
+ }
+ if(iter % (30*60) == 0) {
+ if(Settings.System.getInt(context.getContentResolver(),
+ "screen_brightness_mode", 0) != 0) {
+ writeToLog("setting_brightness automatic\n");
+ } else {
+ int brightness = Settings.System.getInt(
+ context.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, -1);
+ if(brightness != -1) {
+ writeToLog("setting_brightness " + brightness + "\n");
+ }
+ }
+ int timeout = Settings.System.getInt(
+ context.getContentResolver(),
+ Settings.System.SCREEN_OFF_TIMEOUT, -1);
+ if(timeout != -1) {
+ writeToLog("setting_screen_timeout " + timeout + "\n");
+ }
+ String httpProxy = Settings.Secure.getString(
+ context.getContentResolver(),
+ Settings.Secure.HTTP_PROXY);
+ if(httpProxy != null) {
+ writeToLog("setting_httpproxy " + httpProxy + "\n");
+ }
+ }
+
+ /* Let's only grab memory information every 10 seconds to try to keep log
+ * file size down and the notice_data table size down.
+ */
+ boolean hasMem = false;
+ if(iter % 10 == 0) {
+ hasMem = sysInfo.getMemInfo(memInfo);
+ }
+
+ synchronized(fileWriteLock) {
+ if(logStream != null) try {
+ if(firstLogIteration) {
+ firstLogIteration = false;
+ logStream.write("time " + System.currentTimeMillis() + "\n");
+ Calendar cal = new GregorianCalendar();
+ logStream.write("localtime_offset " +
+ (cal.get(Calendar.ZONE_OFFSET) +
+ cal.get(Calendar.DST_OFFSET)) + "\n");
+ logStream.write("model " + phoneConstants.modelName() + "\n");
+ if(NotificationService.available()) {
+ logStream.write("notifications-active\n");
+ }
+ if(bst.hasFullCapacity()) {
+ logStream.write("batt_full_capacity " + bst.getFullCapacity()
+ + "\n");
+ }
+ synchronized(uidAppIds) {
+ for(int uid : uidAppIds.keySet()) {
+ if(uid < SystemInfo.AID_APP) {
+ continue;
+ }
+ logStream.write("associate " + uid + " " + uidAppIds.get(uid)
+ + "\n");
+ }
+ }
+ }
+ logStream.write("begin " + iter + "\n");
+ logStream.write("total-power " + (long)Math.round(totalPower) + '\n');
+ if(hasMem) {
+ logStream.write("meminfo " + memInfo[0] + " " + memInfo[1] +
+ " " + memInfo[2] + " " + memInfo[3] + "\n");
+ }
+ for(int i = 0; i < components; i++) {
+ IterationData data = dataTemp[i];
+ if(data != null) {
+ String name = powerComponents.get(i).getComponentName();
+ SparseArray<PowerData> uidData = data.getUidPowerData();
+ for(int j = 0; j < uidData.size(); j++) {
+ int uid = uidData.keyAt(j);
+ PowerData powerData = uidData.valueAt(j);
+ if(uid == SystemInfo.AID_ALL) {
+ logStream.write(name + " " + (long)Math.round(
+ powerData.getCachedPower()) + "\n");
+ powerData.writeLogDataInfo(logStream);
+ } else {
+ logStream.write(name + "-" + uid + " " + (long)Math.round(
+ powerData.getCachedPower()) + "\n");
+ }
+ }
+ data.recycle();
+ }
+ }
+ } catch(IOException e) {
+ Log.w(TAG, "Failed to write to log file");
+ }
+
+ if(iter % 15 == 0 && prefs.getBoolean("sendPermission", true)) {
+ /* Allow for LogUploader to decide if the log needs to be uploaded and
+ * begin uploading if it decides it's necessary.
+ */
+ if(logUploader.shouldUpload()) {
+ try {
+ logStream.close();
+ } catch(IOException e) {
+ Log.w(TAG, "Failed to flush and close log stream");
+ }
+ logStream = null;
+ logUploader.upload(context.getFileStreamPath(
+ "PowerTrace.log").getAbsolutePath());
+ openLog(false);
+ firstLogIteration = true;
+ }
+ }
+ }
+ }
+
+ /* Blank the widget's display and turn off power button. */
+ PowerWidget.updateWidgetDone(context);
+
+ /* Have all of the power component threads exit. */
+ logUploader.interrupt();
+ for(int i = 0; i < components; i++) {
+ powerComponents.get(i).interrupt();
+ }
+ try {
+ logUploader.join();
+ } catch(InterruptedException e) {
+ }
+ for(int i = 0; i < components; i++) {
+ try {
+ powerComponents.get(i).join();
+ } catch(InterruptedException e) {
+ }
+ }
+
+ /* Close the logstream so that everything gets flushed and written to file
+ * before we have to quit.
+ */
+ synchronized(fileWriteLock) {
+ if(logStream != null) try {
+ logStream.close();
+ } catch(IOException e) {
+ Log.w(TAG, "Failed to flush log file on exit");
+ }
+ }
+ }
+
+ public void plug(boolean plugged) {
+ logUploader.plug(plugged);
+ }
+
+ public void writeToLog(String m) {
+ synchronized(fileWriteLock) {
+ if(logStream != null) try {
+ logStream.write(m);
+ } catch(IOException e) {
+ Log.w(TAG, "Failed to write message to power log");
+ }
+ }
+ }
+
+ public String[] getComponents() {
+ int components = powerComponents.size();
+ String[] ret = new String[components];
+ for(int i = 0; i < components; i++) {
+ ret[i] = powerComponents.get(i).getComponentName();
+ }
+ return ret;
+ }
+
+ public int[] getComponentsMaxPower() {
+ PhoneConstants constants = PhoneSelector.getConstants(context);
+ int components = powerComponents.size();
+ int[] ret = new int[components];
+ for(int i = 0; i < components; i++) {
+ ret[i] = (int)constants.getMaxPower(
+ powerComponents.get(i).getComponentName());
+ }
+ return ret;
+ }
+
+ public int getNoUidMask() {
+ int components = powerComponents.size();
+ int ret = 0;
+ for(int i = 0; i < components; i++) {
+ if(!powerComponents.get(i).hasUidInformation()) {
+ ret |= 1 << i;
+ }
+ }
+ return ret;
+ }
+
+ public int[] getComponentHistory(int count, int componentId, int uid,
+ long iteration) {
+ if(iteration == -1) synchronized(iterationLock) {
+ iteration = lastWrittenIteration;
+ }
+ int components = powerComponents.size();
+ if(componentId == ALL_COMPONENTS) {
+ int[] result = new int[count];
+ for(int i = 0; i < components; i++) {
+ int[] comp = histories.get(i).get(uid, iteration, count);
+ for(int j = 0; j < count; j++) {
+ result[j] += comp[j];
+ }
+ }
+ return result;
+ }
+ if(componentId < 0 || components <= componentId) return null;
+ return histories.get(componentId).get(uid, iteration, count);
+ }
+
+ public long[] getTotals(int uid, int windowType) {
+ int components = powerComponents.size();
+ long[] ret = new long[components];
+ for(int i = 0; i < components; i++) {
+ ret[i] = histories.get(i).getTotal(uid, windowType) *
+ ITERATION_INTERVAL / 1000;
+ }
+ return ret;
+ }
+
+ public long getRuntime(int uid, int windowType) {
+ long runningTime = 0;
+ int components = powerComponents.size();
+ for(int i = 0; i < components; i++) {
+ long entries = histories.get(i).getCount(uid, windowType);
+ runningTime = entries > runningTime ? entries : runningTime;
+ }
+ return runningTime * ITERATION_INTERVAL / 1000;
+ }
+
+ public long[] getMeans(int uid, int windowType) {
+ long[] ret = getTotals(uid, windowType);
+ long runningTime = getRuntime(uid, windowType);
+ runningTime = runningTime == 0 ? 1 : runningTime;
+ for(int i = 0; i < ret.length; i++) {
+ ret[i] /= runningTime;
+ }
+ return ret;
+ }
+
+ public UidInfo[] getUidInfo(int windowType, int ignoreMask) {
+ long iteration;
+ synchronized(iterationLock) {
+ iteration = lastWrittenIteration;
+ }
+ int components = powerComponents.size();
+ synchronized(uidAppIds) {
+ int pos = 0;
+ UidInfo[] result = new UidInfo[uidAppIds.size()];
+ for(Integer uid : uidAppIds.keySet()) {
+ UidInfo info = UidInfo.obtain();
+ int currentPower = 0;
+ for(int i = 0; i < components; i++) {
+ if((ignoreMask & 1 << i) == 0) {
+ currentPower += histories.get(i).get(uid, iteration, 1)[0];
+ }
+ }
+ double scale = ITERATION_INTERVAL / 1000.0;
+ info.init(uid, currentPower,
+ sumArray(getTotals(uid, windowType), ignoreMask) *
+ ITERATION_INTERVAL / 1000,
+ getRuntime(uid, windowType) * ITERATION_INTERVAL / 1000);
+ result[pos++] = info;
+ }
+ return result;
+ }
+ }
+
+ private long sumArray(long[] A, int ignoreMask) {
+ long ret = 0;
+ for(int i = 0; i < A.length; i++) {
+ if((ignoreMask & 1 << i) == 0) {
+ ret += A[i];
+ }
+ }
+ return ret;
+ }
+
+ public long getUidExtra(String name, int uid) {
+ if("OLEDSCORE".equals(name)) {
+ long entries = oledScoreHistory.getCount(uid, Counter.WINDOW_TOTAL);
+ if(entries <= 0) return -2;
+ double result = oledScoreHistory.getTotal(uid, Counter.WINDOW_TOTAL) /
+ 1000.0;
+ result /= entries;
+ PhoneConstants phoneConstants = PhoneSelector.getConstants(context);
+ result *= 255 / (phoneConstants.getMaxPower("OLED") -
+ phoneConstants.oledBasePower());
+ return (long)Math.round(result * 100);
+ }
+ return -1;
+ }
+}
+
diff --git a/src/edu/umich/PowerTutor/service/UMLoggerService.java b/src/edu/umich/PowerTutor/service/UMLoggerService.java
new file mode 100644
index 0000000..0c144df
--- /dev/null
+++ b/src/edu/umich/PowerTutor/service/UMLoggerService.java
@@ -0,0 +1,394 @@
+/*
+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");
+ }
+ };
+}
diff --git a/src/edu/umich/PowerTutor/service/UidInfo.java b/src/edu/umich/PowerTutor/service/UidInfo.java
new file mode 100644
index 0000000..f794f9f
--- /dev/null
+++ b/src/edu/umich/PowerTutor/service/UidInfo.java
@@ -0,0 +1,65 @@
+/*
+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.util.Recycler;
+
+import java.io.Serializable;
+
+public class UidInfo implements Serializable, Comparable {
+ private static Recycler<UidInfo> recycler = new Recycler<UidInfo>();
+
+ public static UidInfo obtain() {
+ UidInfo result = recycler.obtain();
+ if(result != null) return result;
+ return new UidInfo();
+ }
+
+ public void recycle() {
+ recycler.recycle(this);
+ }
+
+ public int uid;
+ public int currentPower;
+ public long totalEnergy;
+ public long runtime;
+
+ public transient double key;
+ public transient double percentage;
+ public transient String unit;
+
+ private UidInfo() {
+ }
+
+ public void init(int uid, int currentPower, long totalEnergy,
+ long runtime) {
+ this.uid = uid;
+ this.currentPower = currentPower;
+ this.totalEnergy = totalEnergy;
+ this.runtime = runtime;
+ }
+
+ public int compareTo(Object o) {
+ UidInfo x = (UidInfo)o;
+ if(key > x.key) return -1;
+ if(key == x.key) return 0;
+ return 1;
+ }
+}
diff --git a/src/edu/umich/PowerTutor/ui/EditPreferences.java b/src/edu/umich/PowerTutor/ui/EditPreferences.java
new file mode 100644
index 0000000..354a60e
--- /dev/null
+++ b/src/edu/umich/PowerTutor/ui/EditPreferences.java
@@ -0,0 +1,32 @@
+/*
+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.ui;
+
+import edu.umich.PowerTutor.R;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+public class EditPreferences extends PreferenceActivity {
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.preferences);
+ }
+}
diff --git a/src/edu/umich/PowerTutor/ui/Help.java b/src/edu/umich/PowerTutor/ui/Help.java
new file mode 100644
index 0000000..67f29c4
--- /dev/null
+++ b/src/edu/umich/PowerTutor/ui/Help.java
@@ -0,0 +1,49 @@
+/*
+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.ui;
+
+import edu.umich.PowerTutor.R;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+
+/**This function implements the UI for help view*/
+public class Help extends Activity {
+ private static final String powerTutorUrl = "http://powertutor.org";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.help);
+ TextView s2 = (TextView)this.findViewById(R.id.S2);
+
+ s2.setOnClickListener(new TextView.OnClickListener() {
+ public void onClick(View v) {
+ Intent myIntent = new Intent(Intent.ACTION_VIEW,
+ Uri.parse(powerTutorUrl));
+ startActivity(myIntent);
+ }
+ });
+ }
+}
diff --git a/src/edu/umich/PowerTutor/ui/MiscView.java b/src/edu/umich/PowerTutor/ui/MiscView.java
new file mode 100644
index 0000000..3b8e484
--- /dev/null
+++ b/src/edu/umich/PowerTutor/ui/MiscView.java
@@ -0,0 +1,478 @@
+/*
+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.ui;
+
+import edu.umich.PowerTutor.R;
+import edu.umich.PowerTutor.phone.PhoneSelector;
+import edu.umich.PowerTutor.service.ICounterService;
+import edu.umich.PowerTutor.service.PowerEstimator;
+import edu.umich.PowerTutor.service.UMLoggerService;
+import edu.umich.PowerTutor.util.Counter;
+import edu.umich.PowerTutor.util.BatteryStats;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ListActivity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.io.File;
+
+public class MiscView extends Activity {
+ private static final String TAG = "MiscView";
+
+ private SharedPreferences prefs;
+ private int uid;
+
+ private Runnable collector;
+
+ private Intent serviceIntent;
+ private CounterServiceConnection conn;
+ private ICounterService counterService;
+ private Handler handler;
+
+ private BatteryStats batteryStats;
+
+ private String[] componentNames;
+
+ public void refreshView() {
+ final ListView listView = new ListView(this);
+
+ ArrayAdapter adapter = new ArrayAdapter(this, 0) {
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View itemView = getLayoutInflater()
+ .inflate(R.layout.misc_item_layout, listView, false);
+ TextView title = (TextView)itemView.findViewById(R.id.title);
+ TextView summary = (TextView)itemView.findViewById(R.id.summary);
+ LinearLayout widgetGroup =
+ (LinearLayout)itemView.findViewById(R.id.widget_frame);
+ InfoItem item = (InfoItem)getItem(position);
+ item.initViews(title, summary, widgetGroup);
+ item.setupView();
+ return itemView;
+ }
+ };
+
+ final ArrayList<InfoItem> allItems = new ArrayList<InfoItem>();
+ allItems.add(new UidItem());
+ allItems.add(new PackageItem());
+ allItems.add(new OLEDItem());
+ allItems.add(new InstantPowerItem());
+ allItems.add(new AveragePowerItem());
+ allItems.add(new CurrentItem());
+ allItems.add(new ChargeItem());
+ allItems.add(new VoltageItem());
+ allItems.add(new TempItem());
+
+ for(InfoItem inf : allItems) {
+ if(inf.available()) {
+ adapter.add(inf);
+ }
+ }
+
+ listView.setAdapter(adapter);
+ setContentView(listView);
+
+ collector = new Runnable() {
+ public void run() {
+ for(InfoItem inf : allItems) {
+ if(inf.available()) {
+ inf.setupView();
+ }
+ }
+ if(handler != null) {
+ handler.postDelayed(this, 2 * PowerEstimator.ITERATION_INTERVAL);
+ }
+ }
+ };
+ if(handler != null) {
+ handler.post(collector);
+ }
+ }
+
+ class CounterServiceConnection implements ServiceConnection {
+ public void onServiceConnected(ComponentName className,
+ IBinder boundService ) {
+ counterService = ICounterService.Stub.asInterface((IBinder)boundService);
+ try {
+ componentNames = counterService.getComponents();
+ } catch(RemoteException e) {
+ componentNames = new String[0];
+ }
+ refreshView();
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ counterService = null;
+ getApplicationContext().unbindService(conn);
+ getApplicationContext().bindService(serviceIntent, conn, 0);
+ Log.w(TAG, "Unexpectedly lost connection to service");
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ uid = getIntent().getIntExtra("uid", SystemInfo.AID_ALL);
+ if(savedInstanceState != null) {
+ componentNames = savedInstanceState.getStringArray("componentNames");
+ }
+ batteryStats = BatteryStats.getInstance();
+ serviceIntent = new Intent(this, UMLoggerService.class);
+ conn = new CounterServiceConnection();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ handler = new Handler();
+ getApplicationContext().bindService(serviceIntent, conn, 0);
+ refreshView();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ getApplicationContext().unbindService(conn);
+ if(collector != null) {
+ handler.removeCallbacks(collector);
+ collector = null;
+ handler = null;
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putStringArray("componentNames", componentNames);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ return false;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ return null;
+ }
+
+ private abstract class InfoItem {
+ protected TextView title;
+ protected TextView summary;
+ protected TextView txt;
+
+ public void initViews(TextView title, TextView summary,
+ LinearLayout widgetGroup) {
+ this.title = title;
+ this.summary = summary;
+ txt = new TextView(MiscView.this);
+ widgetGroup.addView(txt);
+ }
+
+ public abstract boolean available();
+
+ public abstract void setupView();
+ }
+
+ private class CurrentItem extends InfoItem {
+ public boolean available() {
+ return uid == SystemInfo.AID_ALL && batteryStats.hasCurrent();
+ }
+
+ public void setupView() {
+ if(txt == null) return;
+ double current = batteryStats.getCurrent();
+ if(current <= 0) {
+ txt.setText(String.format("%1$.1f mA", -current * 1000));
+ } else {
+ double cp = batteryStats.getCapacity();
+ if(0.01 <= cp && cp <= 0.99 && batteryStats.hasCharge()) {
+ long time = (long)(batteryStats.getCharge() / cp * (1.0 - cp) /
+ current);
+ txt.setText(String.format(
+ "%1$.1f mA\n(Charge time %2$d:%3$02d:%4$02d)", current * 1000,
+ time / 60 / 60, time / 60 % 60, time % 60));
+ }
+ }
+ txt.setGravity(Gravity.CENTER);
+
+ title.setText("Current");
+ summary.setText("Battery current sensor reading");
+ }
+ }
+
+ private class ChargeItem extends InfoItem {
+ public boolean available() {
+ return uid == SystemInfo.AID_ALL && batteryStats.hasCharge();
+ }
+
+ public void setupView() {
+ if(txt == null) return;
+ double charge = batteryStats.getCharge() / 60 / 60 * 1e3; //As->mAh
+ double perc = batteryStats.getCapacity();
+ if(perc < 0) {
+ txt.setText(String.format("%1$.1f mAh", charge));
+ } else {
+ txt.setText(String.format("%1$.1f mAh\n(%2$.0f%%)",
+ charge, 100 * perc));
+ }
+ txt.setGravity(Gravity.CENTER);
+
+ title.setText("Charge");
+ summary.setText("Battery charge sensor reading");
+ }
+ }
+
+ private class InstantPowerItem extends InfoItem {
+ private static final double POLY_WEIGHT = 0.10;
+
+ public boolean available() {
+ return true;
+ }
+
+ public void setupView() {
+ if(txt == null) return;
+ if(counterService != null) try {
+ // Compute what we're going to call the temporal power usage.
+ int count = 0;
+ int[] history = counterService.getComponentHistory(
+ 5 * 60, -1, uid);
+ double weightedAvgPower = 0;
+ for(int i = history.length - 1; i >= 0; i--) {
+ if(history[i] != 0) {
+ count++;
+ weightedAvgPower *= 1.0 - POLY_WEIGHT;
+ weightedAvgPower += POLY_WEIGHT * history[i] / 1000.0;
+ }
+ }
+ if(count > 0) {
+ double charge = batteryStats.getCharge();
+ double volt = batteryStats.getVoltage();
+ if(charge > 0 && volt > 0) {
+ weightedAvgPower /= 1.0 - Math.pow(1.0 - POLY_WEIGHT, count);
+ long time = (long)(charge * volt / weightedAvgPower);
+ txt.setText(String.format("%1$.0f mW\n" +
+ "time: %2$d:%3$02d:%4$02d", weightedAvgPower * 1000.0,
+ time / 60 / 60, time / 60 % 60, time % 60));
+ } else {
+ txt.setText(String.format("%1$.0f mW", weightedAvgPower * 1000.0));
+ }
+ } else {
+ txt.setText("No data");
+ }
+ } catch(RemoteException e) {
+ txt.setText("Error");
+ } else {
+ txt.setText("No data");
+ }
+
+ txt.setGravity(Gravity.CENTER);
+ title.setText("Current Power");
+ summary.setText("Weighted average of power consumption over the last " +
+ "five minutes");
+ }
+ }
+
+ private class AveragePowerItem extends InfoItem {
+ public boolean available() {
+ return true;
+ }
+
+ public void setupView() {
+ if(txt == null) return;
+ if(counterService != null) try {
+ // Compute what we're going to call the temporal power usage.
+ double power = 0;
+ long[] means = counterService.getMeans(uid, Counter.WINDOW_TOTAL);
+ if(means != null) for(long p : means) {
+ power += p / 1000.0;
+ }
+
+ if(power > 0) {
+ double charge = batteryStats.getCharge();
+ double volt = batteryStats.getVoltage();
+ if(charge > 0 && volt > 0) {
+ long time = (long)(charge * volt / power);
+ txt.setText(String.format("%1$.0f mW\n" +
+ "time: %2$d:%3$02d:%4$02d", power * 1000.0,
+ time / 60 / 60, time / 60 % 60, time % 60));
+ } else {
+ txt.setText(String.format("%1$.0f mW", power * 1000.0));
+ }
+ } else {
+ txt.setText("No data");
+ }
+ } catch(RemoteException e) {
+ txt.setText("Error");
+ } else {
+ txt.setText("No data");
+ }
+
+ txt.setGravity(Gravity.CENTER);
+ title.setText("Average Power");
+ summary.setText("Average power consumption since profiler started");
+ }
+ }
+
+ private class VoltageItem extends InfoItem {
+ public boolean available() {
+ return uid == SystemInfo.AID_ALL && batteryStats.hasVoltage();
+ }
+
+ public void setupView() {
+ if(txt == null) return;
+ double voltage = batteryStats.getVoltage();
+ txt.setText(String.format("%1$.2f V", voltage));
+ txt.setGravity(Gravity.CENTER);
+
+ title.setText("Voltage");
+ summary.setText("Battery voltage sensor reading");
+ }
+ }
+
+ private class TempItem extends InfoItem {
+ public boolean available() {
+ return uid == SystemInfo.AID_ALL && batteryStats.hasTemp();
+ }
+
+ public void setupView() {
+ if(txt == null) return;
+ double celcius = batteryStats.getTemp();
+ double farenheit = 32 + celcius * 9.0 / 5.0;
+ txt.setText(String.format("%1$.1f \u00b0C\n(%2$.1f \u00b0F)",
+ celcius, farenheit));
+ txt.setGravity(Gravity.CENTER);
+
+ title.setText("Battery Temperature");
+ summary.setText("Battery temperature sensor reading");
+ }
+ }
+
+ private class UidItem extends InfoItem {
+ public boolean available() {
+ return uid != SystemInfo.AID_ALL;
+ }
+
+ public void setupView() {
+ if(txt == null) return;
+ txt.setText("" + uid);
+ txt.setGravity(Gravity.CENTER);
+
+ title.setText("User ID");
+ summary.setText("User ID for " + SystemInfo.getInstance().getUidName(uid,
+ getApplicationContext().getPackageManager()));
+
+ }
+ }
+
+ private class PackageItem extends InfoItem {
+ public boolean available() {
+ return uid >= SystemInfo.AID_APP;
+ }
+
+ public void setupView() {
+ if(txt == null) return;
+ txt.setText("");
+
+ title.setText("Packages");
+
+ PackageManager pm = getApplicationContext().getPackageManager();
+ String[] packages = pm.getPackagesForUid(uid);
+ if(packages != null) {
+ StringBuilder buf = new StringBuilder();
+ for(String packageName : packages) {
+ if(buf.length() != 0) buf.append("\n");
+ buf.append(packageName);
+ }
+ summary.setText(buf.toString());
+ } else {
+ summary.setText("(None)");
+ }
+ }
+ }
+
+ private class OLEDItem extends InfoItem {
+ public boolean available() {
+ if(uid < SystemInfo.AID_APP) return false;
+ return PhoneSelector.hasOled();
+ }
+
+ public void setupView() {
+ if(txt == null) return;
+
+ txt.setText("No data");
+ if(counterService != null) {
+ try {
+ long score = counterService.getUidExtra("OLEDSCORE", uid);
+ if(score >= 0) {
+ txt.setText("" + (100 - score));
+ }
+ } catch(RemoteException e) {
+ Log.w(TAG, "Failed to request oled score information");
+ }
+ }
+
+ title.setText("OLED Score");
+ summary.setText("100 is highly efficient\n0 is very inefficient\n" +
+ "Independent of brightness");
+ }
+ }
+}
+
diff --git a/src/edu/umich/PowerTutor/ui/PowerPie.java b/src/edu/umich/PowerTutor/ui/PowerPie.java
new file mode 100644
index 0000000..9600059
--- /dev/null
+++ b/src/edu/umich/PowerTutor/ui/PowerPie.java
@@ -0,0 +1,295 @@
+/*
+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.ui;
+
+import org.achartengine.GraphicalView;
+import org.achartengine.chart.PieChart;
+import org.achartengine.model.CategorySeries;
+import org.achartengine.renderer.DefaultRenderer;
+import org.achartengine.renderer.SimpleSeriesRenderer;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import edu.umich.PowerTutor.service.ICounterService;
+import edu.umich.PowerTutor.service.PowerEstimator;
+import edu.umich.PowerTutor.service.UMLoggerService;
+import edu.umich.PowerTutor.util.Counter;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+public class PowerPie extends Activity {
+ private static final String TAG = "PowerPie";
+
+ private SharedPreferences prefs;
+ private int uid;
+
+ private String[] componentNames;
+ private int noUidMask;
+
+ private Runnable collector;
+
+ private Intent serviceIntent;
+ private CounterServiceConnection conn;
+ private ICounterService counterService;
+ private Handler handler;
+
+ private TextView displayText;
+
+ public static final int[] COLORS = new int[] {
+ Color.BLUE, Color.GREEN, Color.MAGENTA, Color.YELLOW,
+ Color.RED, Color.LTGRAY, Color.DKGRAY, Color.CYAN
+ };
+
+ public void refreshView() {
+ if(counterService == null) {
+ TextView loadingText = new TextView(this);
+ loadingText.setText("Waiting for profiler service...");
+ loadingText.setGravity(Gravity.CENTER);
+ setContentView(loadingText);
+ return;
+ }
+
+ if(uid == SystemInfo.AID_ALL) {
+ /* If we are reporting global power usage then just set noUidMask to 0 so
+ * that all components get displayed.
+ */
+ noUidMask = 0;
+ }
+
+ displayText = new TextView(this);
+ displayText.setGravity(Gravity.CENTER);
+ updateDisplayText();
+
+ final CategorySeries series = new CategorySeries("");
+ final DefaultRenderer renderer = new DefaultRenderer();
+ renderer.setLabelsTextSize(15);
+ renderer.setLegendTextSize(15);
+ renderer.setMargins(new int[] { 5, 50, 5, 50 });
+
+ PieChart pieChart = new PieChart(series, renderer);
+ final GraphicalView chartView = new GraphicalView(this, pieChart);
+
+ /* The collector is responsible for periodically updating the screen with
+ * new energy usage information for the current uid.
+ */
+ collector = new Runnable() {
+ public void run() {
+ try {
+ long[] totals = counterService.getTotals(uid,
+ prefs.getInt("pieWindowType", 0));
+ long sumTotal = 0;
+ for(int i = 0; i < totals.length; i++) {
+ totals[i] = totals[i] * PowerEstimator.ITERATION_INTERVAL / 1000;
+ sumTotal += totals[i];
+ }
+ int index = 0;
+ if(sumTotal < 1e-7) {
+ series.set(0, "No data", 1);
+ } else for(int i = 0; i < totals.length; i++) {
+ if((noUidMask & 1 << i) != 0) {
+ continue;
+ }
+ String prefix;
+ double val = totals[i];
+ if(val > 1e12) {
+ prefix = "G";
+ val /= 1e12;
+ } else if(val > 1e9) {
+ prefix = "M";
+ val /= 1e9;
+ } else if(val > 1e6) {
+ prefix = "k";
+ val /= 1e6;
+ } else if(val > 1e3) {
+ prefix = "";
+ val /= 1e3;
+ } else {
+ prefix = "m";
+ }
+
+ String label = String.format("%1$s %2$.1f %3$sJ",
+ componentNames[i], val, prefix);
+ if(series.getItemCount() == index) {
+ SimpleSeriesRenderer r = new SimpleSeriesRenderer();
+ r.setColor(COLORS[i]);
+ renderer.addSeriesRenderer(r);
+
+ series.add(label, totals[i]);
+ } else {
+ series.set(index, label, totals[i]);
+ }
+ index++;
+ }
+ chartView.invalidate();
+ } catch(RemoteException e) {
+ Log.w(TAG, "Failed to contact power tutor profiling service");
+ }
+ if(handler != null) {
+ handler.postDelayed(this, 2 * PowerEstimator.ITERATION_INTERVAL);
+ }
+ }
+ };
+ if(handler != null) {
+ handler.post(collector);
+ }
+
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.addView(displayText);
+ layout.addView(chartView);
+ setContentView(layout);
+ }
+
+ public void updateDisplayText() {
+ displayText.setText("Displaying energy usage over " +
+ Counter.WINDOW_DESCS[prefs.getInt("pieWindowType", 0)] + " for " +
+ (uid == SystemInfo.AID_ALL ? " the entire phone." :
+ SystemInfo.getInstance().getUidName(uid, getPackageManager()) + "."));
+ }
+
+ class CounterServiceConnection implements ServiceConnection {
+ public void onServiceConnected(ComponentName className,
+ IBinder boundService ) {
+ counterService = ICounterService.Stub.asInterface((IBinder)boundService);
+ try {
+ componentNames = counterService.getComponents();
+ noUidMask = counterService.getNoUidMask();
+ refreshView();
+ } catch(RemoteException e) {
+ counterService = null;
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ counterService = null;
+ getApplicationContext().unbindService(conn);
+ getApplicationContext().bindService(serviceIntent, conn, 0);
+ Log.w(TAG, "Unexpectedly lost connection to service");
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ uid = getIntent().getIntExtra("uid", SystemInfo.AID_ALL);
+
+ if(savedInstanceState != null) {
+ componentNames = savedInstanceState.getStringArray("componentNames");
+ noUidMask = savedInstanceState.getInt("noUidMask");
+ }
+
+ serviceIntent = new Intent(this, UMLoggerService.class);
+ conn = new CounterServiceConnection();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ handler = new Handler();
+ getApplicationContext().bindService(serviceIntent, conn, 0);
+
+ refreshView();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ getApplicationContext().unbindService(conn);
+ if(collector != null) {
+ handler.removeCallbacks(collector);
+ handler = null;
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putStringArray("componentNames", componentNames);
+ outState.putInt("noUidMask", noUidMask);
+ }
+
+ private static final int MENU_WINDOW = 0;
+ private static final int DIALOG_WINDOW = 0;
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_WINDOW, 0, "Time Span");
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ /* We need to make sure that the user can't cause any of the dialogs to be
+ * created before we have contacted the Power Tutor service to get the
+ * component names and such.
+ */
+ for(int i = 0; i < menu.size(); i++) {
+ menu.getItem(i).setEnabled(counterService != null);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case MENU_WINDOW:
+ showDialog(DIALOG_WINDOW);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ switch(id) {
+ case DIALOG_WINDOW:
+ builder.setTitle("Select window type");
+ builder.setItems(Counter.WINDOW_NAMES,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ prefs.edit().putInt("pieWindowType", item).commit();
+ updateDisplayText();
+ }
+ });
+ return builder.create();
+ }
+ return null;
+ }
+}
+
diff --git a/src/edu/umich/PowerTutor/ui/PowerTabs.java b/src/edu/umich/PowerTutor/ui/PowerTabs.java
new file mode 100644
index 0000000..5929193
--- /dev/null
+++ b/src/edu/umich/PowerTutor/ui/PowerTabs.java
@@ -0,0 +1,63 @@
+/*
+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.ui;
+
+import edu.umich.PowerTutor.R;
+
+import android.app.TabActivity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.widget.TabHost;
+
+public class PowerTabs extends TabActivity {
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.power_tabs);
+
+ Resources res = getResources();
+ TabHost tabHost = getTabHost();
+ TabHost.TabSpec spec;
+
+ // TODO: We could put in some icons on each of these two tabs. Not sure if
+ // we care enough or if it would look much better.
+ Intent intent = new Intent(this, PowerViewer.class);
+ intent.putExtras(getIntent());
+ spec = tabHost.newTabSpec("Charts").setIndicator("Chart View")
+ .setContent(intent);
+ tabHost.addTab(spec);
+
+ // Do the same for the other tabs
+ intent = new Intent(this, PowerPie.class);
+ intent.putExtras(getIntent());
+ spec = tabHost.newTabSpec("Pie").setIndicator("Pie View")
+ .setContent(intent);
+ tabHost.addTab(spec);
+
+ intent = new Intent(this, MiscView.class);
+ intent.putExtras(getIntent());
+ spec = tabHost.newTabSpec("Stat").setIndicator("Stat View")
+ .setContent(intent);
+ tabHost.addTab(spec);
+
+ // Show the PowerViewer activity by default.
+ tabHost.setCurrentTab(0);
+ }
+}
diff --git a/src/edu/umich/PowerTutor/ui/PowerTop.java b/src/edu/umich/PowerTutor/ui/PowerTop.java
new file mode 100644
index 0000000..21324f6
--- /dev/null
+++ b/src/edu/umich/PowerTutor/ui/PowerTop.java
@@ -0,0 +1,428 @@
+/*
+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.ui;
+
+import edu.umich.PowerTutor.R;
+import edu.umich.PowerTutor.service.ICounterService;
+import edu.umich.PowerTutor.service.PowerEstimator;
+import edu.umich.PowerTutor.service.UMLoggerService;
+import edu.umich.PowerTutor.service.UidInfo;
+import edu.umich.PowerTutor.util.Counter;
+import edu.umich.PowerTutor.util.Recycler;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import java.io.ByteArrayInputStream;
+import java.io.ObjectInputStream;
+import java.io.IOException;
+import java.text.DecimalFormat;
+import java.util.Arrays;
+
+public class PowerTop extends Activity implements Runnable {
+ private static final String TAG = "PowerTop";
+ private static final double HIDE_UID_THRESHOLD = 0.1;
+
+ public static final int KEY_CURRENT_POWER = 0;
+ public static final int KEY_AVERAGE_POWER = 1;
+ public static final int KEY_TOTAL_ENERGY = 2;
+ private static final CharSequence[] KEY_NAMES = { "Current power",
+ "Average power", "Energy usage"};
+
+ private SharedPreferences prefs;
+ private int noUidMask;
+ private String[] componentNames;
+
+ private Intent serviceIntent;
+ private CounterServiceConnection conn;
+ private ICounterService counterService;
+ private Handler handler;
+
+ private LinearLayout topGroup;
+ private LinearLayout filterGroup;
+ private LinearLayout mainView;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ serviceIntent = new Intent(this, UMLoggerService.class);
+ conn = new CounterServiceConnection();
+ if(savedInstanceState != null) {
+ componentNames = savedInstanceState.getStringArray("componentNames");
+ noUidMask = savedInstanceState.getInt("noUidMask");
+ }
+
+ topGroup = new LinearLayout(this);
+ topGroup.setOrientation(LinearLayout.VERTICAL);
+ ScrollView scrollView = new ScrollView(this);
+ scrollView.addView(topGroup);
+ filterGroup = new LinearLayout(this);
+ filterGroup.setOrientation(LinearLayout.HORIZONTAL);
+ filterGroup.setMinimumHeight(50);
+ mainView = new LinearLayout(this);
+ mainView.setOrientation(LinearLayout.VERTICAL);
+ mainView.addView(filterGroup);
+ mainView.addView(scrollView);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ handler = new Handler();
+ handler.postDelayed(this, 100);
+ getApplicationContext().bindService(serviceIntent, conn, 0);
+
+ refreshView();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ getApplicationContext().unbindService(conn);
+ handler.removeCallbacks(this);
+ handler = null;
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putStringArray("componentNames", componentNames);
+ outState.putInt("noUidMask", noUidMask);
+ }
+
+ private static final int MENU_KEY = 0;
+ private static final int MENU_WINDOW = 1;
+ private static final int DIALOG_KEY = 0;
+ private static final int DIALOG_WINDOW = 1;
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_KEY, 0, "Display Type");
+ menu.add(0, MENU_WINDOW, 0, "Time Span");
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ /* We need to make sure that the user can't cause any of the dialogs to be
+ * created before we have contacted the Power Tutor service to get the
+ * component names and such.
+ */
+ for(int i = 0; i < menu.size(); i++) {
+ menu.getItem(i).setEnabled(counterService != null);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case MENU_KEY:
+ showDialog(DIALOG_KEY);
+ return true;
+ case MENU_WINDOW:
+ showDialog(DIALOG_WINDOW);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ switch(id) {
+ case DIALOG_KEY:
+ builder.setTitle("Select sort key");
+ builder.setItems(KEY_NAMES, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ prefs.edit().putInt("topKeyId", item).commit();
+ }
+ });
+ return builder.create();
+ case DIALOG_WINDOW:
+ builder.setTitle("Select window type");
+ builder.setItems(Counter.WINDOW_NAMES,
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int item) {
+ prefs.edit().putInt("topWindowType", item).commit();
+ }
+ });
+ return builder.create();
+ }
+ return null;
+ }
+
+ private void refreshView() {
+ if(counterService == null) {
+ TextView loadingText = new TextView(this);
+ loadingText.setText("Waiting for profiler service...");
+ loadingText.setGravity(Gravity.CENTER);
+ setContentView(loadingText);
+ return;
+ }
+
+ int keyId = prefs.getInt("topKeyId", KEY_TOTAL_ENERGY);
+ try {
+ byte[] rawUidInfo = counterService.getUidInfo(
+ prefs.getInt("topWindowType", Counter.WINDOW_TOTAL),
+ noUidMask | prefs.getInt("topIgnoreMask", 0));
+ if(rawUidInfo != null) {
+ UidInfo[] uidInfos = (UidInfo[])new ObjectInputStream(
+ new ByteArrayInputStream(rawUidInfo)).readObject();
+ double total = 0;
+ for(UidInfo uidInfo : uidInfos) {
+ if(uidInfo.uid == SystemInfo.AID_ALL) continue;
+ switch(keyId) {
+ case KEY_CURRENT_POWER:
+ uidInfo.key = uidInfo.currentPower;
+ uidInfo.unit = "W";
+ break;
+ case KEY_AVERAGE_POWER:
+ uidInfo.key = uidInfo.totalEnergy /
+ (uidInfo.runtime == 0 ? 1 : uidInfo.runtime);
+ uidInfo.unit = "W";
+ break;
+ case KEY_TOTAL_ENERGY:
+ uidInfo.key = uidInfo.totalEnergy;
+ uidInfo.unit = "J";
+ break;
+ default:
+ uidInfo.key = uidInfo.currentPower;
+ uidInfo.unit = "W";
+ }
+ total += uidInfo.key;
+ }
+ if(total == 0) total = 1;
+ for(UidInfo uidInfo : uidInfos) {
+ uidInfo.percentage = 100.0 * uidInfo.key / total;
+ }
+ Arrays.sort(uidInfos);
+
+ int sz = 0;
+ for(int i = 0; i < uidInfos.length; i++) {
+ if(uidInfos[i].uid == SystemInfo.AID_ALL ||
+ uidInfos[i].percentage < HIDE_UID_THRESHOLD) {
+ continue;
+ }
+ UidPowerView powerView;
+ if(sz < topGroup.getChildCount()) {
+ powerView = (UidPowerView)topGroup.getChildAt(sz);
+ } else {
+ powerView = UidPowerView.obtain(this, getIntent());
+ topGroup.addView(powerView);
+ }
+ powerView.setBackgroundDrawable(null);
+ powerView.setBackgroundColor((sz & 1) == 0 ? 0xFF000000 :
+ 0xFF222222);
+ powerView.init(uidInfos[i], keyId);
+ sz++;
+ }
+ for(int i = sz; i < topGroup.getChildCount(); i++) {
+ UidPowerView powerView = (UidPowerView)topGroup.getChildAt(i);
+ powerView.recycle();
+ }
+ topGroup.removeViews(sz, topGroup.getChildCount() - sz);
+ }
+ } catch(IOException e) {
+ } catch(RemoteException e) {
+ } catch(ClassNotFoundException e) {
+ } catch(ClassCastException e) {
+ }
+ setContentView(mainView);
+ if(keyId == KEY_CURRENT_POWER) {
+ setTitle(KEY_NAMES[keyId]);
+ } else {
+ setTitle(KEY_NAMES[keyId] + " over " +
+ Counter.WINDOW_DESCS[prefs.getInt("topWindowType",
+ Counter.WINDOW_TOTAL)]);
+ }
+ }
+
+ public void run() {
+ refreshView();
+ if(handler != null) {
+ handler.postDelayed(this, 2 * PowerEstimator.ITERATION_INTERVAL);
+ }
+ }
+
+ private static class UidPowerView extends LinearLayout {
+ private static Recycler<UidPowerView> recycler =
+ new Recycler<UidPowerView>();
+ private static DecimalFormat formatter = new DecimalFormat("0.0");
+
+ public static UidPowerView obtain(Activity activity, Intent startIntent) {
+ UidPowerView result = recycler.obtain();
+ if(result == null) return new UidPowerView(activity, startIntent);
+ return result;
+ }
+
+ public void recycle() {
+ recycler.recycle(this);
+ }
+
+ private UidInfo uidInfo;
+ private String name;
+ private Drawable icon;
+
+ private ImageView imageView;
+ private TextView textView;
+
+ private UidPowerView(final Activity activity, final Intent startIntent) {
+ super(activity);
+ setMinimumHeight(50);
+ setOrientation(LinearLayout.HORIZONTAL);
+ imageView = new ImageView(activity);
+ imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+ imageView.setAdjustViewBounds(true);
+ imageView.setMaxHeight(40);
+ imageView.setMaxWidth(40);
+ imageView.setMinimumWidth(50);
+ imageView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.FILL_PARENT));
+ textView = new TextView(activity);
+ textView.setGravity(Gravity.CENTER_VERTICAL);
+ textView.setLayoutParams(new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT));
+ addView(imageView);
+ addView(textView);
+ setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ Intent viewIntent = new Intent(v.getContext(), PowerTabs.class);
+ viewIntent.putExtras(startIntent);
+ viewIntent.putExtra("uid", uidInfo.uid);
+ activity.startActivityForResult(viewIntent, 0);
+ }
+ });
+ setFocusable(true);
+ }
+
+ public void init(UidInfo uidInfo, int keyType) {
+ SystemInfo sysInfo = SystemInfo.getInstance();
+ this.uidInfo = uidInfo;
+ PackageManager pm = getContext().getPackageManager();
+ name = sysInfo.getUidName(uidInfo.uid, pm);
+ icon = sysInfo.getUidIcon(uidInfo.uid, pm);
+ imageView.setImageDrawable(icon);
+ String prefix;
+ if(uidInfo.key > 1e12) {
+ prefix = "G";
+ uidInfo.key /= 1e12;
+ } else if(uidInfo.key > 1e9) {
+ prefix = "M";
+ uidInfo.key /= 1e9;
+ } else if(uidInfo.key > 1e6) {
+ prefix = "k";
+ uidInfo.key /= 1e6;
+ } else if(uidInfo.key > 1e3) {
+ prefix = "";
+ uidInfo.key /= 1e3;
+ } else {
+ prefix = "m";
+ }
+ long secs = (long)Math.round(uidInfo.runtime);
+
+ textView.setText(String.format("%1$.1f%% [%3$d:%4$02d:%5$02d] %2$s\n" +
+ "%6$.1f %7$s%8$s",
+ uidInfo.percentage, name, secs / 60 / 60, (secs / 60) % 60,
+ secs % 60, uidInfo.key, prefix, uidInfo.unit));
+ }
+ }
+
+ private class CounterServiceConnection implements ServiceConnection {
+ public void onServiceConnected(ComponentName className,
+ IBinder boundService ) {
+ counterService = ICounterService.Stub.asInterface((IBinder)boundService);
+ try {
+ componentNames = counterService.getComponents();
+ noUidMask = counterService.getNoUidMask();
+ filterGroup.removeAllViews();
+ for(int i = 0; i < componentNames.length; i++) {
+ int ignMask = prefs.getInt("topIgnoreMask", 0);
+ if((noUidMask & 1 << i) != 0) continue;
+ final TextView filterToggle = new TextView(PowerTop.this);
+ final int index = i;
+ filterToggle.setText(componentNames[i]);
+ filterToggle.setGravity(Gravity.CENTER);
+ filterToggle.setTextColor((ignMask & 1 << index) == 0 ?
+ 0xFFFFFFFF : 0xFF888888);
+ filterToggle.setBackgroundColor(
+ filterGroup.getChildCount() % 2 == 0 ? 0xFF444444 : 0xFF555555);
+ filterToggle.setFocusable(true);
+ filterToggle.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ int ignMask = prefs.getInt("topIgnoreMask", 0);
+ if((ignMask & 1 << index) == 0) {
+ prefs.edit().putInt("topIgnoreMask", ignMask | 1 << index)
+ .commit();
+ filterToggle.setTextColor(0xFF888888);
+ } else {
+ prefs.edit().putInt("topIgnoreMask", ignMask & ~(1 << index))
+ .commit();
+ filterToggle.setTextColor(0xFFFFFFFF);
+ }
+ }
+ });
+ filterGroup.addView(filterToggle,
+ new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.FILL_PARENT, 1f));
+ }
+ } catch(RemoteException e) {
+ counterService = null;
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ counterService = null;
+ getApplicationContext().unbindService(conn);
+ getApplicationContext().bindService(serviceIntent, conn, 0);
+ Log.w(TAG, "Unexpectedly lost connection to service");
+ }
+ }
+}
+
diff --git a/src/edu/umich/PowerTutor/ui/PowerViewer.java b/src/edu/umich/PowerTutor/ui/PowerViewer.java
new file mode 100644
index 0000000..7eadec8
--- /dev/null
+++ b/src/edu/umich/PowerTutor/ui/PowerViewer.java
@@ -0,0 +1,343 @@
+/*
+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.ui;
+
+import org.achartengine.GraphicalView;
+import org.achartengine.chart.CubicLineChart;
+import org.achartengine.model.XYMultipleSeriesDataset;
+import org.achartengine.model.XYSeries;
+import org.achartengine.renderer.XYMultipleSeriesRenderer;
+import org.achartengine.renderer.XYSeriesRenderer;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import edu.umich.PowerTutor.service.ICounterService;
+import edu.umich.PowerTutor.service.PowerEstimator;
+import edu.umich.PowerTutor.service.UMLoggerService;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+public class PowerViewer extends Activity {
+ private static final String TAG = "PowerViewer";
+
+ private SharedPreferences prefs;
+ private int uid;
+
+ private int components;
+ private String[] componentNames;
+ private int[] componentsMaxPower;
+ private int noUidMask;
+ private boolean collecting;
+
+ private ValueCollector[] collectors;
+
+ private Intent serviceIntent;
+ private CounterServiceConnection conn;
+ private ICounterService counterService;
+
+ private Handler handler;
+ private LinearLayout chartLayout;
+
+ public void refreshView() {
+ if(counterService == null) {
+ TextView loadingText = new TextView(this);
+ loadingText.setText("Waiting for profiler service...");
+ loadingText.setGravity(Gravity.CENTER);
+ setContentView(loadingText);
+ return;
+ }
+
+ chartLayout = new LinearLayout(this);
+ chartLayout.setOrientation(LinearLayout.VERTICAL);
+
+ if(uid == SystemInfo.AID_ALL) {
+ /* If we are reporting global power usage then just set noUidMask to 0 so
+ * that all components get displayed.
+ */
+ noUidMask = 0;
+ }
+ components = 0;
+ for(int i = 0; i < componentNames.length; i++) {
+ if((noUidMask & 1 << i) == 0) {
+ components++;
+ }
+ }
+ boolean showTotal = prefs.getBoolean("showTotalPower", false);
+ collectors = new ValueCollector[(showTotal ? 1 : 0) + components];
+
+ int pos = 0;
+ for(int i = showTotal ? -1 : 0; i < componentNames.length; i++) {
+ if(i != -1 && (noUidMask & 1 << i) != 0) {
+ continue;
+ }
+ String name = i == -1 ? "Total" : componentNames[i];
+ double mxPower = (i == -1 ? 2100.0 : componentsMaxPower[i]) * 1.05;
+
+ XYSeries series = new XYSeries(name);
+ XYMultipleSeriesDataset mseries = new XYMultipleSeriesDataset();
+ mseries.addSeries(series);
+
+ XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
+ XYSeriesRenderer srenderer = new XYSeriesRenderer();
+ renderer.setYAxisMin(0.0);
+ renderer.setYAxisMax(mxPower);
+ renderer.setYTitle(name + "(mW)");
+
+ int clr = PowerPie.COLORS[(PowerPie.COLORS.length + i) %
+ PowerPie.COLORS.length];
+ srenderer.setColor(clr);
+ srenderer.setFillBelowLine(true);
+ srenderer.setFillBelowLineColor(((clr >> 1) & 0x7F7F7F) |
+ (clr & 0xFF000000));
+ renderer.addSeriesRenderer(srenderer);
+
+ View chartView = new GraphicalView(this,
+ new CubicLineChart(mseries, renderer, 0.5f));
+ chartView.setMinimumHeight(100);
+ chartLayout.addView(chartView);
+
+ collectors[pos] = new ValueCollector(series, renderer, chartView, i);
+ if(handler != null) {
+ handler.post(collectors[pos]);
+ }
+ pos++;
+ }
+
+ /* We're giving 100 pixels per graph of vertical space for the chart view.
+ If we don't specify a minimum height the chart view ends up having a
+ height of 0 so this is important. */
+ chartLayout.setMinimumHeight(100 * components);
+
+ ScrollView scrollView = new ScrollView(this);
+ scrollView.addView(chartLayout);
+ setContentView(scrollView);
+ }
+
+ private class CounterServiceConnection implements ServiceConnection {
+ public void onServiceConnected(ComponentName className,
+ IBinder boundService) {
+ counterService = ICounterService.Stub.asInterface((IBinder)boundService);
+ try {
+ componentNames = counterService.getComponents();
+ componentsMaxPower = counterService.getComponentsMaxPower();
+ noUidMask = counterService.getNoUidMask();
+ refreshView();
+ } catch(RemoteException e) {
+ counterService = null;
+ }
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ counterService = null;
+ getApplicationContext().unbindService(conn);
+ getApplicationContext().bindService(serviceIntent, conn, 0);
+ Log.w(TAG, "Unexpectedly lost connection to service");
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ uid = getIntent().getIntExtra("uid", SystemInfo.AID_ALL);
+
+ collecting = true;
+ if(savedInstanceState != null) {
+ collecting = savedInstanceState.getBoolean("collecting", true);
+ componentNames = savedInstanceState.getStringArray("componentNames");
+ noUidMask = savedInstanceState.getInt("noUidMask");
+ }
+
+ serviceIntent = new Intent(this, UMLoggerService.class);
+ conn = new CounterServiceConnection();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ handler = new Handler();
+ getApplicationContext().bindService(serviceIntent, conn, 0);
+
+ refreshView();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ getApplicationContext().unbindService(conn);
+ if(collectors != null) for(int i = 0; i < components; i++) {
+ handler.removeCallbacks(collectors[i]);
+ }
+ counterService = null;
+ handler = null;
+ collecting = true;
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putBoolean("collecting", collecting);
+ outState.putStringArray("componentNames", componentNames);
+ outState.putInt("noUidMask", noUidMask);
+ }
+
+ /* Let all of the UI graphs lay themselves out again. */
+ private void stateChanged() {
+ for(int i = 0; i < components; i++) {
+ collectors[i].layout();
+ }
+ }
+
+ private static final int MENU_OPTIONS = 0;
+ private static final int MENU_TOGGLE_COLLECTING = 1;
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_OPTIONS, 0, "Options");
+ menu.add(0, MENU_TOGGLE_COLLECTING, 0, "");
+ return true;
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ menu.findItem(MENU_TOGGLE_COLLECTING).setTitle(
+ collecting ? "Pause" : "Resume");
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case MENU_OPTIONS:
+ startActivity(new Intent(this, ViewerPreferences.class));
+ return true;
+ case MENU_TOGGLE_COLLECTING:
+ collecting = !collecting;
+ if(handler != null) {
+ if(collecting) for(int i = 0; i < components; i++) {
+ collectors[i].reset();
+ handler.post(collectors[i]);
+ } else for(int i = 0; i < components; i++) {
+ handler.removeCallbacks(collectors[i]);
+ }
+ }
+ break;
+ }
+ return false;
+ }
+
+ public class ValueCollector implements Runnable {
+ private XYSeries series;
+ private XYMultipleSeriesRenderer renderer;
+ private View chartView;
+
+ private int componentId;
+ private long lastTime;
+
+ int[] values;
+
+ private boolean readHistory;
+
+ public ValueCollector(XYSeries series, XYMultipleSeriesRenderer renderer,
+ View chartView, int componentId) {
+ this.series = series;
+ this.renderer = renderer;
+ this.chartView = chartView;
+ this.componentId = componentId;
+ lastTime = SystemClock.elapsedRealtime();
+ layout();
+ }
+
+ public void layout() {
+ int numVals = Integer.parseInt(prefs.getString("viewNumValues_s", "60"));
+ values = new int[numVals];
+
+ renderer.clearXTextLabels();
+ renderer.setXAxisMin(0);
+ renderer.setXAxisMax(numVals - 1);
+ renderer.addXTextLabel(numVals - 1, "" + numVals);
+ renderer.setXLabels(0);
+ for(int j = 0; j < 10; j++) {
+ renderer.addXTextLabel(numVals * j / 10, "" + (1 + numVals * j / 10));
+ }
+
+ reset();
+ }
+
+ /** Restart points collecting from zero. */
+ public void reset() {
+ series.clear();
+ readHistory = true;
+ }
+
+ public void run() {
+ int numVals = Integer.parseInt(prefs.getString("viewNumValues_s", "60"));
+ if(counterService != null) try {
+ if(readHistory) {
+ values = counterService.getComponentHistory(numVals,
+ componentId, uid);
+ readHistory = false;
+ } else {
+ for(int i = numVals - 1; i > 0; i--) {
+ values[i] = values[i - 1];
+ }
+ values[0] = counterService.getComponentHistory(1, componentId,
+ uid)[0];
+ }
+ } catch(RemoteException e) {
+ Log.w(TAG, "Failed to get data from service");
+ for(int i = 0; i < numVals; i++) {
+ values[i] = 0;
+ }
+ }
+
+ series.clear();
+ for(int i = 0; i < numVals; i++) {
+ series.add(i, values[i]);
+ }
+
+ long curTime = SystemClock.elapsedRealtime();
+ long tryTime = lastTime + PowerEstimator.ITERATION_INTERVAL *
+ (long)Math.max(1, 1 + (curTime - lastTime) /
+ PowerEstimator.ITERATION_INTERVAL);
+ if(handler != null) {
+ handler.postDelayed(this, tryTime - curTime);
+ }
+
+ chartView.invalidate();
+ }
+ };
+}
diff --git a/src/edu/umich/PowerTutor/ui/StartupReceiver.java b/src/edu/umich/PowerTutor/ui/StartupReceiver.java
new file mode 100644
index 0000000..df3a3d2
--- /dev/null
+++ b/src/edu/umich/PowerTutor/ui/StartupReceiver.java
@@ -0,0 +1,41 @@
+/*
+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.ui;
+
+import edu.umich.PowerTutor.service.UMLoggerService;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+public class StartupReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ SharedPreferences prefs =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ if(prefs.getBoolean("runOnStartup", true)) {
+ Intent serviceIntent = new Intent(context, UMLoggerService.class);
+ context.startService(serviceIntent);
+ }
+ }
+}
+
diff --git a/src/edu/umich/PowerTutor/ui/UMLogger.java b/src/edu/umich/PowerTutor/ui/UMLogger.java
new file mode 100644
index 0000000..ebaca34
--- /dev/null
+++ b/src/edu/umich/PowerTutor/ui/UMLogger.java
@@ -0,0 +1,355 @@
+/*
+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.ui;
+
+import edu.umich.PowerTutor.R;
+import edu.umich.PowerTutor.phone.PhoneSelector;
+import edu.umich.PowerTutor.service.ICounterService;
+import edu.umich.PowerTutor.service.UMLoggerService;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.util.zip.InflaterInputStream;
+import java.io.BufferedOutputStream;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/** The main view activity for PowerTutor*/
+public class UMLogger extends Activity {
+ private static final String TAG = "UMLogger";
+
+ public static final String CURRENT_VERSION = "1.2"; // Don't change this...
+
+ public static final String SERVER_IP = "spidermonkey.eecs.umich.edu";
+ public static final int SERVER_PORT = 5204;
+
+ private SharedPreferences prefs;
+ private Intent serviceIntent;
+ private ICounterService counterService;
+ private CounterServiceConnection conn;
+
+ private Button serviceStartButton;
+ private Button appViewerButton;
+ private Button sysViewerButton;
+ private Button helpButton;
+ private TextView scaleText;
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ prefs = PreferenceManager.getDefaultSharedPreferences(this);
+ serviceIntent = new Intent(this, UMLoggerService.class);
+ conn = new CounterServiceConnection();
+
+ setContentView(R.layout.main);
+ ArrayAdapter<?> adapterxaxis = ArrayAdapter.createFromResource(
+ this, R.array.xaxis, android.R.layout.simple_spinner_item);
+ adapterxaxis.setDropDownViewResource(
+ android.R.layout.simple_spinner_dropdown_item);
+
+ serviceStartButton = (Button)findViewById(R.id.servicestartbutton);
+ appViewerButton = (Button)findViewById(R.id.appviewerbutton);
+ sysViewerButton = (Button)findViewById(R.id.sysviewerbutton);
+ helpButton= (Button)findViewById(R.id.helpbutton);
+
+ serviceStartButton.setOnClickListener(serviceStartButtonListener);
+ sysViewerButton.setOnClickListener(sysViewerButtonListener);
+ appViewerButton.setOnClickListener(appViewerButtonListener);
+ helpButton.setOnClickListener(helpButtonListener);
+
+ if(counterService != null) {
+ serviceStartButton.setText("Stop Profiler");
+ appViewerButton.setEnabled(true);
+ sysViewerButton.setEnabled(true);
+ } else {
+ serviceStartButton.setText("Start Profiler");
+ appViewerButton.setEnabled(false);
+ sysViewerButton.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ getApplicationContext().bindService(serviceIntent, conn, 0);
+ if(prefs.getBoolean("firstRun", true)) {
+ if(PhoneSelector.getPhoneType() == PhoneSelector.PHONE_UNKNOWN) {
+ showDialog(DIALOG_UNKNOWN_PHONE);
+ } else {
+ showDialog(DIALOG_TOS);
+ }
+ }
+ Intent startingIntent = getIntent();
+ if(startingIntent.getBooleanExtra("isFromIcon", false)) {
+ Intent copyIntent = (Intent)getIntent().clone();
+ copyIntent.putExtra("isFromIcon", false);
+ setIntent(copyIntent);
+ Intent intent = new Intent(this, PowerTabs.class);
+ startActivity(intent);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ getApplicationContext().unbindService(conn);
+ }
+
+ private static final int MENU_PREFERENCES = 0;
+ private static final int MENU_SAVE_LOG = 1;
+ private static final int DIALOG_START_SENDING = 0;
+ private static final int DIALOG_STOP_SENDING = 1;
+ private static final int DIALOG_TOS = 2;
+ private static final int DIALOG_RUNNING_ON_STARTUP = 3;
+ private static final int DIALOG_NOT_RUNNING_ON_STARTUP = 4;
+ private static final int DIALOG_UNKNOWN_PHONE = 5;
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, MENU_PREFERENCES, 0, "Options");
+ menu.add(0, MENU_SAVE_LOG, 0, "Save log");
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case MENU_PREFERENCES:
+ startActivity(new Intent(this, EditPreferences.class));
+ return true;
+ case MENU_SAVE_LOG:
+ new Thread() {
+ public void start() {
+ File writeFile = new File(
+ Environment.getExternalStorageDirectory(), "PowerTrace" +
+ System.currentTimeMillis() + ".log");
+ try {
+ InflaterInputStream logIn = new InflaterInputStream(
+ openFileInput("PowerTrace.log"));
+ BufferedOutputStream logOut = new BufferedOutputStream(
+ new FileOutputStream(writeFile));
+
+ byte[] buffer = new byte[20480];
+ for(int ln = logIn.read(buffer); ln != -1;
+ ln = logIn.read(buffer)) {
+ logOut.write(buffer, 0, ln);
+ }
+ logIn.close();
+ logOut.close();
+ Toast.makeText(UMLogger.this, "Wrote log to " +
+ writeFile.getAbsolutePath(),
+ Toast.LENGTH_SHORT).show();
+ return;
+ } catch(java.io.EOFException e) {
+ Toast.makeText(UMLogger.this, "Wrote log to " +
+ writeFile.getAbsolutePath(),
+ Toast.LENGTH_SHORT).show();
+ return;
+ } catch(IOException e) {
+ }
+ Toast.makeText(UMLogger.this, "Failed to write log to sdcard",
+ Toast.LENGTH_SHORT).show();
+ }
+ }.start();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ /**This function includes all the dialog constructor*/
+ @Override
+ protected Dialog onCreateDialog(int id) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ switch(id) {
+ case DIALOG_TOS:
+ builder.setMessage(R.string.term)
+ .setCancelable(false)
+ .setPositiveButton("Agree", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ prefs.edit().putBoolean("firstRun", false)
+ .putBoolean("runOnStartup", true)
+ .putBoolean("sendPermission", true).commit();
+ dialog.dismiss();
+ }
+ })
+ .setNegativeButton("Do not agree",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ prefs.edit().putBoolean("firstRun", true).commit();
+ finish();
+ }
+ });
+ return builder.create();
+ case DIALOG_STOP_SENDING:
+ builder.setMessage(R.string.stop_sending_text)
+ .setCancelable(true)
+ .setPositiveButton("Stop", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ prefs.edit().putBoolean("sendPermission", false).commit();
+ dialog.dismiss();
+ }
+ })
+ .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ return builder.create();
+ case DIALOG_START_SENDING:
+ builder.setMessage(R.string.start_sending_text)
+ .setCancelable(true)
+ .setPositiveButton("Start", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ prefs.edit().putBoolean("sendPermission", true).commit();
+ dialog.dismiss();
+ }
+ })
+ .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ return builder.create();
+ case DIALOG_RUNNING_ON_STARTUP:
+ builder.setMessage(R.string.running_on_startup)
+ .setCancelable(true)
+ .setNeutralButton("Ok", null);
+ return builder.create();
+ case DIALOG_NOT_RUNNING_ON_STARTUP:
+ builder.setMessage(R.string.not_running_on_startup)
+ .setCancelable(true)
+ .setNeutralButton("Ok", null);
+ return builder.create();
+ case DIALOG_UNKNOWN_PHONE:
+ builder.setMessage(R.string.unknown_phone)
+ .setCancelable(false)
+ .setNeutralButton("Ok", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.dismiss();
+ showDialog(DIALOG_TOS);
+ }
+ });
+ return builder.create();
+
+ }
+ return null;
+ }
+
+
+ private Button.OnClickListener appViewerButtonListener =
+ new Button.OnClickListener() {
+ public void onClick(View v) {
+ Intent intent = new Intent(v.getContext(), PowerTop.class);
+ startActivityForResult(intent, 0);
+ }
+ };
+
+ private Button.OnClickListener sysViewerButtonListener =
+ new Button.OnClickListener() {
+ public void onClick(View v) {
+ Intent intent = new Intent(v.getContext(), PowerTabs.class);
+ startActivityForResult(intent, 0);
+ }
+ };
+
+ private Button.OnClickListener serviceStartButtonListener =
+ new Button.OnClickListener() {
+ public void onClick(View v) {
+ serviceStartButton.setEnabled(false);
+ if(counterService != null) {
+ stopService(serviceIntent);
+ } else {
+ if(conn == null) {
+ Toast.makeText(UMLogger.this, "Profiler failed to start",
+ Toast.LENGTH_SHORT).show();
+ } else {
+ startService(serviceIntent);
+ }
+ }
+ }
+ };
+
+ private class CounterServiceConnection implements ServiceConnection {
+ public void onServiceConnected(ComponentName className,
+ IBinder boundService) {
+ counterService = ICounterService.Stub.asInterface((IBinder)boundService);
+ serviceStartButton.setText("Stop Profiler");
+ serviceStartButton.setEnabled(true);
+ appViewerButton.setEnabled(true);
+ sysViewerButton.setEnabled(true);
+ }
+
+ public void onServiceDisconnected(ComponentName className) {
+ counterService = null;
+ getApplicationContext().unbindService(conn);
+ getApplicationContext().bindService(serviceIntent, conn, 0);
+
+ Toast.makeText(UMLogger.this, "Profiler stopped",
+ Toast.LENGTH_SHORT).show();
+ serviceStartButton.setText("Start Profiler");
+ serviceStartButton.setEnabled(true);
+ appViewerButton.setEnabled(false);
+ sysViewerButton.setEnabled(false);
+ }
+ }
+
+ private Button.OnClickListener helpButtonListener =
+ new Button.OnClickListener() {
+ public void onClick(View v) {
+ Intent myIntent = new Intent(v.getContext(), Help.class);
+ startActivityForResult(myIntent, 0);
+ }
+ };
+}
diff --git a/src/edu/umich/PowerTutor/ui/ViewerPreferences.java b/src/edu/umich/PowerTutor/ui/ViewerPreferences.java
new file mode 100644
index 0000000..525df62
--- /dev/null
+++ b/src/edu/umich/PowerTutor/ui/ViewerPreferences.java
@@ -0,0 +1,32 @@
+/*
+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.ui;
+
+import edu.umich.PowerTutor.R;
+
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+
+public class ViewerPreferences extends PreferenceActivity {
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ addPreferencesFromResource(R.xml.viewer_preferences);
+ }
+}
diff --git a/src/edu/umich/PowerTutor/util/BatteryStats.java b/src/edu/umich/PowerTutor/util/BatteryStats.java
new file mode 100644
index 0000000..1448b12
--- /dev/null
+++ b/src/edu/umich/PowerTutor/util/BatteryStats.java
@@ -0,0 +1,214 @@
+/*
+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.util;
+
+import java.io.File;
+
+public class BatteryStats {
+ private static final String TAG = "BatteryStats";
+ private static BatteryStats instance = null;
+
+ public static BatteryStats getInstance() {
+ if(instance == null) {
+ instance = new BatteryStats();
+ }
+ return instance;
+ }
+
+ private static final String[] VOLTAGE_FILES = {
+ "/sys/class/power_supply/battery/voltage_now",
+ "/sys/class/power_supply/battery/batt_vol",
+ };
+ private static final double[] VOLTAGE_CONV = {
+ 1e-6, // Source in microvolts.
+ 1e-3, // Source in millivolts.
+ };
+
+ private static final String[] CURRENT_FILES = {
+ "/sys/class/power_supply/battery/current_now",
+ //"/sys/class/power_supply/battery/batt_current", Doesn't seem good
+ };
+ private static final double[] CURRENT_CONV = {
+ 1e-6, // Source in microamps.
+ //1e-3, // Source in milliamps.
+ };
+
+ private static final String[] TEMP_FILES = {
+ "/sys/class/power_supply/battery/temp",
+ "/sys/class/power_supply/battery/batt_temp",
+ };
+ private static final double[] TEMP_CONV = {
+ 1e-1, // Source in tenths of a centigrade.
+ 1e-1, // Source in tenths of a centigrade.
+ };
+
+ private static final String[] CHARGE_FILES = {
+ "/sys/class/power_supply/battery/charge_counter",
+ };
+ private static final double[] CHARGE_CONV = {
+ 60*60*1e-6, // Source in micro amp hours.
+ };
+
+ private static final String[] CAPACITY_FILES = {
+ "/sys/class/power_supply/battery/capacity",
+ };
+ private static final double[] CAPACITY_CONV = {
+ 1e-2, // Source in percentage.
+ };
+
+ private static final String[] FULL_CAPACITY_FILES = {
+ "/sys/class/power_supply/battery/full_bat",
+ };
+ private static final double[] FULL_CAPACITY_CONV = {
+ 60*60*1e-6, // Source in micro amp hours.
+ };
+
+ SystemInfo sysInfo;
+
+ String voltageFile;
+ String currentFile;
+ String tempFile;
+ String chargeFile;
+ String capacityFile;
+ String fullCapacityFile;
+
+ double voltageConv;
+ double currentConv;
+ double tempConv;
+ double chargeConv;
+ double capacityConv;
+ double fullCapacityConv;
+
+ private BatteryStats() {
+ sysInfo = SystemInfo.getInstance();
+
+ // Get voltage information.
+ for(int i = 0; i < VOLTAGE_FILES.length; i++) {
+ if(new File(VOLTAGE_FILES[i]).exists()) {
+ voltageFile = VOLTAGE_FILES[i];
+ voltageConv = VOLTAGE_CONV[i];
+ }
+ }
+
+ // Get current information.
+ for(int i = 0; i < CURRENT_FILES.length; i++) {
+ if(new File(CURRENT_FILES[i]).exists()) {
+ currentFile = CURRENT_FILES[i];
+ currentConv = CURRENT_CONV[i];
+ }
+ }
+
+ // Get temperature information.
+ for(int i = 0; i < TEMP_FILES.length; i++) {
+ if(new File(TEMP_FILES[i]).exists()) {
+ tempFile = TEMP_FILES[i];
+ tempConv = TEMP_CONV[i];
+ }
+ }
+
+ // Get charge information.
+ for(int i = 0; i < CHARGE_FILES.length; i++) {
+ if(new File(CHARGE_FILES[i]).exists()) {
+ chargeFile = CHARGE_FILES[i];
+ chargeConv = CHARGE_CONV[i];
+ }
+ }
+
+ // Get capacity information.
+ for(int i = 0; i < CAPACITY_FILES.length; i++) {
+ if(new File(CAPACITY_FILES[i]).exists()) {
+ capacityFile = CAPACITY_FILES[i];
+ capacityConv = CAPACITY_CONV[i];
+ }
+ }
+
+ // Get full capacity information.
+ for(int i = 0; i < FULL_CAPACITY_FILES.length; i++) {
+ if(new File(FULL_CAPACITY_FILES[i]).exists()) {
+ fullCapacityFile = FULL_CAPACITY_FILES[i];
+ fullCapacityConv = FULL_CAPACITY_CONV[i];
+ }
+ }
+ }
+
+ public boolean hasVoltage() {
+ return voltageFile != null;
+ }
+
+ public double getVoltage() {
+ if(voltageFile == null) return -1.0;
+ long volt = sysInfo.readLongFromFile(voltageFile);
+ return volt == -1 ? -1.0 : voltageConv * volt;
+ }
+
+ public boolean hasCurrent() {
+ return currentFile != null;
+ }
+
+ public double getCurrent() {
+ long curr = sysInfo.readLongFromFile(currentFile);
+ return curr == -1 ? -1.0 : currentConv * curr;
+ }
+
+ public boolean hasTemp() {
+ return tempFile != null;
+ }
+
+ public double getTemp() {
+ if(tempFile == null) return -1.0;
+ long temp = sysInfo.readLongFromFile(tempFile);
+ return temp == -1 ? -1.0 : tempConv * temp;
+ }
+
+ public boolean hasCharge() {
+ return chargeFile != null ||
+ hasFullCapacity() && hasCapacity();
+ }
+
+ public double getCharge() {
+ if(chargeFile == null) {
+ double r1 = getCapacity();
+ double r2 = getFullCapacity();
+ return r1 < 0 || r2 < 0 ? -1.0 : r1 * r2;
+ }
+ long charge = sysInfo.readLongFromFile(chargeFile);
+ return charge == -1 ? -1.0 : chargeConv * charge;
+ }
+
+ public boolean hasCapacity() {
+ return capacityFile != null;
+ }
+
+ public double getCapacity() {
+ if(capacityFile == null) return -1.0;
+ long cap = sysInfo.readLongFromFile(capacityFile);
+ return cap == -1 ? -1.0 : capacityConv * cap;
+ }
+
+ public boolean hasFullCapacity() {
+ return fullCapacityFile != null;
+ }
+
+ public double getFullCapacity() {
+ if(fullCapacityFile == null) return -1.0;
+ long cap = sysInfo.readLongFromFile(fullCapacityFile);
+ return cap == -1 ? -1.0 : fullCapacityConv * cap;
+ }
+}
diff --git a/src/edu/umich/PowerTutor/util/Counter.java b/src/edu/umich/PowerTutor/util/Counter.java
new file mode 100644
index 0000000..0508d93
--- /dev/null
+++ b/src/edu/umich/PowerTutor/util/Counter.java
@@ -0,0 +1,118 @@
+/*
+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.util;
+
+import android.os.SystemClock;
+
+public class Counter {
+ public static final int WINDOW_MINUTE = 0;
+ public static final int WINDOW_HOUR = 1;
+ public static final int WINDOW_DAY = 2;
+ public static final int WINDOW_TOTAL = 3;
+ public static final CharSequence[] WINDOW_NAMES = { "Last minute",
+ "Last Hour", "Last Day", "Total"};
+ // To be used for constructions like "Showing X over ..."
+ public static final CharSequence[] WINDOW_DESCS = { "the last minute",
+ "the last hour", "the last day", "all time"};
+ private static final long WINDOW_DURATIONS[] = { 60 * 1000, 60 * 60 * 1000,
+ 24 * 60 * 60 * 1000};
+
+ private long startTime;
+ private long total;
+ private SingleCounter[] counters;
+
+ public Counter() {
+ total = 0;
+ startTime = SystemClock.elapsedRealtime();
+ counters = new SingleCounter[WINDOW_DURATIONS.length];
+ for(int i = 0; i < counters.length; i++) {
+ counters[i] = new SingleCounter();
+ }
+ }
+
+ public void add(long x) {
+ total += x;
+ long now = SystemClock.elapsedRealtime() - startTime;
+ for(int i = 0; i < counters.length; i++) {
+ counters[i].add(x, now * SingleCounter.BUCKETS / WINDOW_DURATIONS[i]);
+ }
+ }
+
+ public long get(int window) {
+ if(window == WINDOW_TOTAL) {
+ return total;
+ }
+ long now = SystemClock.elapsedRealtime() - startTime;
+ return counters[window].get(
+ now * SingleCounter.BUCKETS / WINDOW_DURATIONS[window],
+ (1.0 * now * SingleCounter.BUCKETS % WINDOW_DURATIONS[window]) /
+ WINDOW_DURATIONS[window]);
+ }
+
+ private static class SingleCounter {
+ public static final int BUCKETS = 60;
+
+ private long base;
+ private int baseIdx;
+ private long droppingBucket;
+ private long[] bucketSum;
+ private long total;
+
+ public SingleCounter() {
+ bucketSum = new long[BUCKETS];
+ }
+
+ private void wind(long now) {
+ if(base + 2 * BUCKETS <= now) {
+ /* Completly clear the data structure. */
+ droppingBucket = 0;
+ for(int i = 0; i < BUCKETS; i++) {
+ bucketSum[i] = 0;
+ }
+ total = 0;
+ base = now;
+ baseIdx = 0;
+ } else while(base + BUCKETS <= now) {
+ droppingBucket = bucketSum[baseIdx];
+ total -= droppingBucket;
+ bucketSum[baseIdx] = 0;
+ base++;
+ baseIdx = baseIdx + 1 == BUCKETS ? 0 : baseIdx + 1;
+ }
+ }
+
+ public void add(long x, long now) {
+ wind(now);
+ total += x;
+ int idx = (int)(baseIdx + now - base);
+ bucketSum[idx < BUCKETS ? idx : idx - BUCKETS] += x;
+ }
+
+ /* now gives the time slice that we want information for.
+ * prog, between 0 and 1, gives the progress through the current time slice
+ * with 0 indicating that it just started and 1 indicating that it is about
+ * to end.
+ */
+ public long get(long now, double prog) {
+ wind(now);
+ return total + (long)((1.0 - prog) * droppingBucket);
+ }
+ }
+}
diff --git a/src/edu/umich/PowerTutor/util/ForegroundDetector.java b/src/edu/umich/PowerTutor/util/ForegroundDetector.java
new file mode 100644
index 0000000..a38a3ae
--- /dev/null
+++ b/src/edu/umich/PowerTutor/util/ForegroundDetector.java
@@ -0,0 +1,114 @@
+/*
+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.util;
+
+import edu.umich.PowerTutor.util.SystemInfo;
+
+import android.app.ActivityManager;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+
+/* This detector looks for transitions where one app leaves the foreground and
+ * another enters the foreground to detect apps that are legitimately in the
+ * foreground. If no application is known to be legitimate system is returned.
+ */
+public class ForegroundDetector {
+ int lastSize;
+ int[] lastUids;
+ int nowSize;
+ int[] nowUids;
+
+ private BitSet validated;
+
+ private ActivityManager activityManager;
+
+ public ForegroundDetector(ActivityManager activityManager) {
+ lastSize = nowSize = 0;
+ lastUids = new int[10];
+ nowUids = new int[10];
+ validated = new BitSet(1 << 16);
+ validated.set(android.os.Process.myUid());
+ this.activityManager = activityManager;
+ }
+
+ // Figure out what uid should be charged for screen usage.
+ public int getForegroundUid() {
+ SystemInfo sysInfo = SystemInfo.getInstance();
+ List<ActivityManager.RunningAppProcessInfo> appProcs =
+ activityManager.getRunningAppProcesses();
+
+ // Move the last iteration to last and resize the other array if needed.
+ int[] tmp = lastUids;
+ lastUids = nowUids;
+ lastSize = nowSize;
+ if(tmp.length < appProcs.size()) {
+ tmp = new int[appProcs.size()];
+ }
+ nowUids = tmp;
+
+ // Fill in the uids from appProcs.
+ nowSize = 0;
+ for(ActivityManager.RunningAppProcessInfo app : appProcs) {
+ if(app.importance ==
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+ int uid = sysInfo.getUidForPid(app.pid);
+ if(SystemInfo.AID_APP <= uid && uid < 1 << 16) {
+ nowUids[nowSize++] = uid;
+ }
+ }
+ }
+ Arrays.sort(nowUids, 0, nowSize);
+
+ // Find app-exit app-enter transitions.
+ int appExit = -1;
+ int appEnter = -1;
+ int indNow = 0;
+ int indLast = 0;
+ while(indNow < nowSize && indLast < lastSize) {
+ if(nowUids[indNow] == lastUids[indLast]) {
+ indNow++; indLast++;
+ } else if(nowUids[indNow] < lastUids[indLast]) {
+ appEnter = nowUids[indNow++];
+ } else {
+ appExit = lastUids[indLast++];
+ }
+ }
+ if(indNow < nowSize) appEnter = nowUids[indNow];
+ if(indLast < lastSize) appExit = lastUids[indLast];
+
+ // Found an interesting transition. Validate both applications.
+ if(appEnter != -1 && appExit != -1) {
+ validated.set(appEnter);
+ validated.set(appExit);
+ }
+
+ // Now find a valid application now. Hopefully there is only one. If there
+ // are none return system. If there are several return the one with the
+ // highest uid.
+ for(int i = nowSize - 1; i >= 0; i--) {
+ if(validated.get(nowUids[i])) {
+ return nowUids[i];
+ }
+ }
+ return SystemInfo.AID_SYSTEM;
+ }
+}
diff --git a/src/edu/umich/PowerTutor/util/HexEncode.java b/src/edu/umich/PowerTutor/util/HexEncode.java
new file mode 100644
index 0000000..eb9d975
--- /dev/null
+++ b/src/edu/umich/PowerTutor/util/HexEncode.java
@@ -0,0 +1,41 @@
+/*
+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.util;
+
+public class HexEncode {
+ public static String encode(byte[] bytes) {
+ StringBuilder bld = new StringBuilder();
+ for(int i = 0; i < bytes.length; i++) {
+ bld.append((char)('a' + (bytes[i] >> 4 & 0xF)));
+ bld.append((char)('a' + (bytes[i] & 0xF)));
+ }
+ return bld.toString();
+ }
+
+ public static byte[] decode(String dat) {
+ int N = dat.length() / 2;
+ byte[] ret = new byte[N];
+ for(int i = 0; i < N; i++) {
+ ret[i] = (byte)(dat.charAt(2 * i) - 'a' << 4 |
+ dat.charAt(2 * i + 1) - 'a');
+ }
+ return ret;
+ }
+}
diff --git a/src/edu/umich/PowerTutor/util/HistoryBuffer.java b/src/edu/umich/PowerTutor/util/HistoryBuffer.java
new file mode 100644
index 0000000..65fe0a1
--- /dev/null
+++ b/src/edu/umich/PowerTutor/util/HistoryBuffer.java
@@ -0,0 +1,136 @@
+/*
+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.util;
+
+import android.util.SparseArray;
+
+import java.util.LinkedList;
+import java.util.ListIterator;
+
+public class HistoryBuffer {
+ private static class UidData {
+ public LinkedList<HistoryDatum> queue;
+ public Counter sum;
+ public Counter count;
+
+ public UidData() {
+ queue = new LinkedList<HistoryDatum>();
+ sum = new Counter();
+ count = new Counter();
+ }
+ }
+
+ private static class HistoryDatum {
+ public HistoryDatum() {
+ }
+
+ public void init(long iteration, int power) {
+ this.iteration = iteration;
+ this.power = power;
+ }
+
+ public long iteration;
+ public int power;
+ }
+
+ private int maxSize;
+ private SparseArray<UidData> uidData;
+
+ public HistoryBuffer(int maxSize) {
+ this.maxSize = maxSize;
+ uidData = new SparseArray<UidData>();
+ }
+
+ /* The iteration should only increase across successive adds. */
+ public synchronized void add(int uid, long iteration, int power) {
+ UidData data = uidData.get(uid);
+ if(data == null) {
+ data = new UidData();
+ uidData.put(uid, data);
+ }
+ data.count.add(1);
+ if(power == 0) {
+ return;
+ }
+ data.sum.add(power);
+ if(maxSize == 0) {
+ return;
+ }
+
+ LinkedList<HistoryDatum> queue = data.queue;
+ HistoryDatum datum;
+ if(maxSize <= queue.size()) {
+ datum = queue.getLast();
+ queue.removeLast();
+ } else {
+ datum = new HistoryDatum();
+ }
+ datum.init(iteration, power);
+ queue.addFirst(datum);
+ }
+
+ /* Fills in the previous number timestamps starting from a timestamp and
+ * working backwards. Any timestamp with no information is just treated
+ * as using no power.
+ */
+ public synchronized int[] get(int uid, long timestamp, int number) {
+ int ind = 0;
+ if(number < 0) number = 0;
+ if(number > maxSize) number = maxSize;
+ int[] ret = new int[number];
+ UidData data = uidData.get(uid);
+ LinkedList<HistoryDatum> queue = data == null ? null : data.queue;
+ if(queue == null || queue.isEmpty()) {
+ return ret;
+ }
+ if(timestamp == -1) {
+ timestamp = queue.getFirst().iteration;
+ }
+ for(ListIterator<HistoryDatum> iter = queue.listIterator();
+ iter.hasNext(); ) {
+ HistoryDatum datum = iter.next();
+ while(datum.iteration < timestamp && ind < number) {
+ ind++;
+ timestamp--;
+ }
+ if(ind == number) {
+ break;
+ }
+ if(datum.iteration == timestamp) {
+ ret[ind++] = datum.power;
+ timestamp--;
+ } else {
+ /* datum happened after requested interval. */
+ }
+ }
+ return ret;
+ }
+
+ public synchronized long getTotal(int uid, int windowType) {
+ UidData data = uidData.get(uid);
+ return data == null ? 0 : data.sum.get(windowType);
+ }
+
+ public synchronized long getCount(int uid, int windowType) {
+ UidData data = uidData.get(uid);
+ return data == null ? 0 : data.count.get(windowType);
+ }
+}
+
diff --git a/src/edu/umich/PowerTutor/util/NativeLoader.java b/src/edu/umich/PowerTutor/util/NativeLoader.java
new file mode 100644
index 0000000..926f5bb
--- /dev/null
+++ b/src/edu/umich/PowerTutor/util/NativeLoader.java
@@ -0,0 +1,45 @@
+/*
+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.util;
+
+import android.util.Log;
+
+public class NativeLoader {
+ private static final String TAG = "NativeLoader";
+
+ private static boolean loadOk = false;
+
+ static {
+ try {
+ System.loadLibrary("bindings");
+ loadOk = true;
+ } catch(SecurityException e) {
+ Log.w(TAG, "Failed to load jni dll, will fall back on pure java");
+ loadOk = false;
+ } catch(UnsatisfiedLinkError e) {
+ Log.w(TAG, "Failed to load jni dll, will fall back on pure java");
+ loadOk = false;
+ }
+ }
+
+ public static boolean jniLoaded() {
+ return loadOk;
+ }
+}
diff --git a/src/edu/umich/PowerTutor/util/NotificationService.java b/src/edu/umich/PowerTutor/util/NotificationService.java
new file mode 100644
index 0000000..c5539b2
--- /dev/null
+++ b/src/edu/umich/PowerTutor/util/NotificationService.java
@@ -0,0 +1,354 @@
+/*
+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.util;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+import java.util.Vector;
+
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.util.Log;
+import edu.umich.PowerTutor.PowerNotifications;
+
+@SuppressWarnings("unchecked")
+public class NotificationService {
+ private static final String TAG = "NotificationService";
+
+ /* We haven't tried to install the hook yet. */
+ private static final int STATE_INIT = 0;
+ /* The hook was installed successfully and we should be receiving power
+ * related notifications from the battery service.
+ */
+ private static final int STATE_HOOK_INSTALLED = 1;
+ /* The hook failed to install. This should be the case for most phones as a
+ * hack is required to get this to work.
+ */
+ private static final int STATE_HOOK_FAILED = 2;
+
+ private static int hookState = STATE_INIT;
+ private static Binder notifier = new NotificationForwarder();
+ private static Vector<PowerNotifications> hooks =
+ new Vector<PowerNotifications>();
+
+ private static Method methodGetService;
+
+ static {
+ try {
+ Class classServiceManager = Class.forName("android.os.ServiceManager");
+ methodGetService = classServiceManager.getMethod("getService", String.class);
+ } catch(NoSuchMethodException e) {
+ Log.w(TAG, "Could not find method gerService");
+ } catch(ClassNotFoundException e) {
+ Log.w(TAG, "Could not find class android.os.ServiceManager");
+ }
+ }
+
+ private static IBinder getBatteryService() {
+ if(methodGetService == null) return null;
+ try {
+ return (IBinder)methodGetService.invoke(null, "batteryhook");
+ } catch(InvocationTargetException e) {
+ Log.w(TAG, "Call to get service failed");
+ } catch(IllegalAccessException e) {
+ Log.w(TAG, "Call to get service failed");
+ }
+ return null;
+ }
+
+ public static boolean available() {
+ synchronized(hooks) {
+ if(hookState == STATE_INIT) {
+ return getBatteryService() != null;
+ }
+ return hookState == STATE_HOOK_INSTALLED;
+ }
+ }
+
+ public static void addHook(PowerNotifications notif) {
+ synchronized(hooks) {
+ if(hookState == STATE_INIT) {
+ installHook();
+ }
+ if(hookState != STATE_HOOK_INSTALLED) {
+ Log.w(TAG, "Attempted to add hook though no " +
+ "notification service available");
+ } else {
+ hooks.add(notif);
+ }
+ }
+ }
+
+ public static void removeHook(PowerNotifications notif) {
+ synchronized(hooks) {
+ hooks.remove(notif);
+ }
+ }
+
+ private static void installHook() {
+ Parcel outBinder = Parcel.obtain();
+ outBinder.writeStrongBinder(notifier);
+ hookState = STATE_HOOK_FAILED;
+ try {
+ IBinder batteryHook = getBatteryService();
+ if(batteryHook == null) {
+ /* This should be the case on un-hacked phone. Maybe one day
+ * phones will support this service or similar by default.
+ */
+ Log.i(TAG, "No power notification hook service installed");
+ } else if(!batteryHook.transact(0, outBinder, null, 0)) {
+ Log.w(TAG, "Failed to register forwarder");
+ } else {
+ hookState = STATE_HOOK_INSTALLED;
+ }
+ } catch(RemoteException e) {
+ Log.w(TAG, "Failed to register forwarder");
+ }
+ outBinder.recycle();
+ }
+
+ /* Class responsible for forwarding power notifications to registered
+ * hooks.
+ */
+ private static class NotificationForwarder extends DefaultReceiver {
+ public boolean onTransact(int code, Parcel data,
+ Parcel reply, int flags) throws RemoteException {
+ synchronized(hooks) {
+ for(Iterator<PowerNotifications> iter = hooks.iterator();
+ iter.hasNext(); ) {
+ Parcel junk = Parcel.obtain();
+ try {
+ iter.next().asBinder().transact(code, data, junk, flags);
+ } catch(RemoteException e) {
+ iter.remove();
+ }
+ data.setDataPosition(0);
+ junk.recycle();
+ }
+ }
+ return super.onTransact(code, data, reply, flags);
+ }
+ }
+
+ /* If you only want to receive a subset of the notifications just extend this
+ * class and override the methods you care about.
+ */
+ public static class DefaultReceiver extends PowerNotifications.Stub {
+ public void noteSystemMediaCall(int uid) {}
+ public void noteStartMedia(int uid, int id) {}
+ public void noteStopMedia(int uid, int id) {}
+ public void noteVideoSize(int uid, int id, int width, int height) {}
+ public void noteStartWakelock(int uid, String name, int type) {}
+ public void noteStopWakelock(int uid, String name, int type) {}
+ public void noteStartSensor(int uid, int sensor) {}
+ public void noteStopSensor(int uid, int sensor) {}
+ public void noteStartGps(int uid) {}
+ public void noteStopGps(int uid) {}
+ public void noteScreenOn() {}
+ public void noteScreenBrightness(int brightness) {}
+ public void noteScreenOff() {}
+ public void noteInputEvent() {}
+ public void noteUserActivity(int uid, int event) {}
+ public void notePhoneOn() {}
+ public void notePhoneOff() {}
+ public void notePhoneDataConnectionState(int dataType, boolean hasData) {}
+ public void noteWifiOn(int uid) {}
+ public void noteWifiOff(int uid) {}
+ public void noteWifiRunning() {}
+ public void noteWifiStopped() {}
+ public void noteBluetoothOn() {}
+ public void noteBluetoothOff() {}
+ public void noteFullWifiLockAcquired(int uid) {}
+ public void noteFullWifiLockReleased(int uid) {}
+ public void noteScanWifiLockAcquired(int uid) {}
+ public void noteScanWifiLockReleased(int uid) {}
+ public void noteWifiMulticastEnabled(int uid) {}
+ public void noteWifiMulticastDisabled(int uid) {}
+ public void setOnBattery(boolean onBattery, int level) {}
+ public void recordCurrentLevel(int level) {}
+ public void noteVideoOn(int uid) {}
+ public void noteVideoOff(int uid) {}
+ public void noteAudioOn(int uid) {}
+ public void noteAudioOff(int uid) {}
+ }
+
+ /* Useful for debugging purposes. */
+ public static class PrintNotifications extends PowerNotifications.Stub {
+ public void noteSystemMediaCall(int uid) {
+ System.out.println("System media call[uid=" + uid + "]");
+ }
+
+ public void noteStartMedia(int uid, int id) {
+ System.out.println("Start media[uid=" + uid + ", id=" + id + "]");
+ }
+
+ public void noteStopMedia(int uid, int id) {
+ System.out.println("Stop media[uid=" + uid + ", id=" + id + "]");
+ }
+
+ public void noteVideoSize(int uid, int id, int width, int height) {
+ System.out.println("Video size[uid=" + uid + ", id=" + id +
+ ", width=" + width + ", height=" + height + "]");
+ }
+
+ public void noteStartWakelock(int uid, String name, int type) {
+ System.out.println("Start wakelock[uid=" + uid + ", name=" + name +
+ ", type=" + type + "]");
+ }
+
+ public void noteStopWakelock(int uid, String name, int type) {
+ System.out.println("Stop wakelock[uid=" + uid + ", name=" + name +
+ ", type=" + type + "]");
+ }
+
+ public void noteStartSensor(int uid, int sensor) {
+ System.out.println("noteStartSensor[uid=" + uid + ", sensor=" + sensor +
+ "]");
+ }
+
+ public void noteStopSensor(int uid, int sensor) {
+ System.out.println("noteStopSensor[uid=" + uid + ", sensor=" + sensor +
+ "]");
+ }
+
+ public void noteStartGps(int uid) {
+ System.out.println("noteStartGps[uid=" + uid + "]");
+ }
+
+ public void noteStopGps(int uid) {
+ System.out.println("noteStopGps[uid=" + uid + "]");
+ }
+
+ public void noteScreenOn() {
+ System.out.println("noteScreenOn");
+ }
+
+ public void noteScreenBrightness(int brightness) {
+ System.out.println("noteScreenBrightness[brightness=" + brightness + "]");
+ }
+
+ public void noteScreenOff() {
+ System.out.println("noteScreenOff");
+ }
+
+ public void noteInputEvent() {
+ System.out.println("noteInputEvent");
+ }
+
+ public void noteUserActivity(int uid, int event) {
+ System.out.println("noteUserActivity[uid=" + uid + ", event=" + event +
+ "]");
+ }
+
+ public void notePhoneOn() {
+ System.out.println("notePhoneOn");
+ }
+
+ public void notePhoneOff() {
+ System.out.println("notePhoneOff");
+ }
+
+ public void notePhoneDataConnectionState(int dataType, boolean hasData) {
+ System.out.println("notePhoneDataConnectionState[dataType=" + dataType +
+ ", hasData=" + hasData + "]");
+ }
+
+ public void notePhoneState(int phoneState) {
+ System.out.println("notePhoneState[phoneState=" + phoneState + "]");
+ }
+
+ public void noteWifiOn(int uid) {
+ System.out.println("noteWifiOn[uid=" + uid + "]");
+ }
+
+ public void noteWifiOff(int uid) {
+ System.out.println("noteWifiOff[uid=" + uid + "]");
+ }
+
+ public void noteWifiRunning() {
+ System.out.println("noteWifiRunning");
+ }
+
+ public void noteWifiStopped() {
+ System.out.println("noteWifiStopped");
+ }
+
+ public void noteBluetoothOn() {
+ System.out.println("noteBluetoothOn");
+ }
+
+ public void noteBluetoothOff() {
+ System.out.println("noteBluetoothOff");
+ }
+
+ public void noteFullWifiLockAcquired(int uid) {
+ System.out.println("noteFullWifiLockAcquired[uid=" + uid + "]");
+ }
+
+ public void noteFullWifiLockReleased(int uid) {
+ System.out.println("noteFullWifiLockReleased[uid=" + uid + "]");
+ }
+
+ public void noteScanWifiLockAcquired(int uid) {
+ System.out.println("noteScanWifiLockAcquired[uid=" + uid + "]");
+ }
+
+ public void noteScanWifiLockReleased(int uid) {
+ System.out.println("noteScanWifiLockReleased[uid=" + uid + "]");
+ }
+
+ public void noteWifiMulticastEnabled(int uid) {
+ System.out.println("noteWifiMulticastEnabled[uid=" + uid + "]");
+ }
+
+ public void noteWifiMulticastDisabled(int uid) {
+ System.out.println("noteWifiMulticastDisabled[uid=" + uid + "]");
+
+ }
+
+ public void setOnBattery(boolean onBattery, int level) {
+ System.out.println("setOnBattery[onBattery=" + onBattery + ", level=" +
+ level + "]");
+ }
+
+ public void recordCurrentLevel(int level) {
+ System.out.println("recordCurrentLevel[level=" + level + "]");
+ }
+
+ public void noteVideoOn(int uid) {
+ System.out.println("noteVideoOn[uid=" + uid + "]");
+ }
+
+ public void noteVideoOff(int uid) {
+ System.out.println("noteVideoOff[uid=" + uid + "]");
+ }
+
+ public void noteAudioOn(int uid) {
+ System.out.println("noteAudioOn[uid=" + uid + "]");
+ }
+
+ public void noteAudioOff(int uid) {
+ System.out.println("noteAudioOff[uid=" + uid + "]");
+ }
+ }
+}
diff --git a/src/edu/umich/PowerTutor/util/Recycler.java b/src/edu/umich/PowerTutor/util/Recycler.java
new file mode 100644
index 0000000..677f3db
--- /dev/null
+++ b/src/edu/umich/PowerTutor/util/Recycler.java
@@ -0,0 +1,52 @@
+/*
+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.util;
+
+import java.util.Vector;
+
+/* The aim of this class is to reduce the amount of objects that need to be
+ * created and destroyed every iteration. If we can avoid having to allocate
+ * objects on the heap we can ease the job of the garbage collector and be
+ * more efficient.
+ */
+public class Recycler<T> {
+ private Vector<T> list;
+ private int avail;
+
+ public Recycler() {
+ list = new Vector<T>();
+ avail = 0;
+ }
+
+ public synchronized T obtain() {
+ if(avail == 0) {
+ return null;
+ }
+ return list.get(--avail);
+ }
+
+ public synchronized void recycle(T a) {
+ if(avail < list.size()) {
+ list.set(avail++, a);
+ } else {
+ list.add(a);
+ }
+ }
+}
diff --git a/src/edu/umich/PowerTutor/util/SystemInfo.java b/src/edu/umich/PowerTutor/util/SystemInfo.java
new file mode 100644
index 0000000..9799438
--- /dev/null
+++ b/src/edu/umich/PowerTutor/util/SystemInfo.java
@@ -0,0 +1,573 @@
+/*
+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.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import android.app.ActivityManager;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.Log;
+import android.util.SparseArray;
+
+public class SystemInfo {
+ private static final String TAG = "SystemInfo";
+ private static SystemInfo instance = new SystemInfo();
+
+ public static SystemInfo getInstance() {
+ return instance;
+ }
+
+ /* Uids as listed in android_filesystem_config.h */
+ public static final int AID_ALL = -1; /* A special constant we will
+ * use to indicate a request
+ * for global information. */
+ public static final int AID_ROOT = 0; /* traditional unix root user
+ */
+ public static final int AID_SYSTEM = 1000; /* system server */
+ public static final int AID_RADIO = 1001; /* telephony subsystem, RIL */
+ public static final int AID_BLUETOOTH = 1002; /* bluetooth subsystem */
+ public static final int AID_GRAPHICS = 1003; /* graphics devices */
+ public static final int AID_INPUT = 1004; /* input devices */
+ public static final int AID_AUDIO = 1005; /* audio devices */
+ public static final int AID_CAMERA = 1006; /* camera devices */
+ public static final int AID_LOG = 1007; /* log devices */
+ public static final int AID_COMPASS = 1008; /* compass device */
+ public static final int AID_MOUNT = 1009; /* mountd socket */
+ public static final int AID_WIFI = 1010; /* wifi subsystem */
+ public static final int AID_ADB = 1011; /* android debug bridge
+ (adbd) */
+ public static final int AID_INSTALL = 1012; /* group for installing
+ packages */
+ public static final int AID_MEDIA = 1013; /* mediaserver process */
+ public static final int AID_DHCP = 1014; /* dhcp client */
+ public static final int AID_SHELL = 2000; /* adb and debug shell user */
+ public static final int AID_CACHE = 2001; /* cache access */
+ public static final int AID_DIAG = 2002; /* access to diagnostic
+ resources */
+ /* The 3000 series are intended for use as supplemental group id's only.
+ * They indicate special Android capabilities that the kernel is aware of. */
+ public static final int AID_NET_BT_ADMIN= 3001; /* bluetooth: create any
+ socket */
+ public static final int AID_NET_BT = 3002; /* bluetooth: create sco,
+ rfcomm or l2cap sockets */
+ public static final int AID_INET = 3003; /* can create AF_INET and
+ AF_INET6 sockets */
+ public static final int AID_NET_RAW = 3004; /* can create raw INET sockets
+ */
+ public static final int AID_MISC = 9998; /* access to misc storage */
+ public static final int AID_NOBODY = 9999;
+ public static final int AID_APP =10000; /* first app user */
+
+ /* These are stolen from Process.java which hides these constants. */
+ public static final int PROC_SPACE_TERM = (int)' ';
+ public static final int PROC_TAB_TERM = (int)'\t';
+ public static final int PROC_LINE_TERM = (int)'\n';
+ public static final int PROC_COMBINE = 0x100;
+ public static final int PROC_OUT_LONG = 0x2000;
+ private static final int[] READ_LONG_FORMAT = new int[] {
+ PROC_SPACE_TERM|PROC_OUT_LONG
+ };
+ private static final int[] PROCESS_STATS_FORMAT = new int[] {
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM|PROC_OUT_LONG, // 13: utime
+ PROC_SPACE_TERM|PROC_OUT_LONG // 14: stime
+ };
+ private static final int[] PROCESS_TOTAL_STATS_FORMAT = new int[] {
+ PROC_SPACE_TERM,
+ PROC_SPACE_TERM|PROC_OUT_LONG,
+ PROC_SPACE_TERM|PROC_OUT_LONG,
+ PROC_SPACE_TERM|PROC_OUT_LONG,
+ PROC_SPACE_TERM|PROC_OUT_LONG,
+ PROC_SPACE_TERM|PROC_OUT_LONG,
+ PROC_SPACE_TERM|PROC_OUT_LONG,
+ PROC_SPACE_TERM|PROC_OUT_LONG,
+ };
+ private static final int[] PROC_MEMINFO_FORMAT = new int[] {
+ PROC_SPACE_TERM|PROC_COMBINE, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_LINE_TERM,
+ PROC_SPACE_TERM|PROC_COMBINE, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_LINE_TERM,
+ PROC_SPACE_TERM|PROC_COMBINE, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_LINE_TERM,
+ PROC_SPACE_TERM|PROC_COMBINE, PROC_SPACE_TERM|PROC_OUT_LONG, PROC_LINE_TERM,
+ };
+
+ public static final int INDEX_USER_TIME = 0;
+ public static final int INDEX_SYS_TIME = 1;
+ public static final int INDEX_TOTAL_TIME = 2;
+
+ public static final int INDEX_MEM_TOTAL = 0;
+ public static final int INDEX_MEM_FREE = 1;
+ public static final int INDEX_MEM_BUFFERS = 2;
+ public static final int INDEX_MEM_CACHED = 3;
+
+ /* We are going to take advantage of the hidden API within Process.java that
+ * makes use of JNI so that we can perform the top task efficiently.
+ */
+ private Field fieldUid;
+ private Method methodGetUidForPid;
+ private Method methodGetPids;
+ private Method methodReadProcFile;
+ private Method methodGetProperty;
+
+ private long[] readBuf;
+
+ @SuppressWarnings("unchecked")
+ private SystemInfo() {
+ try {
+ fieldUid = ActivityManager.RunningAppProcessInfo.class.getField("uid");
+ } catch(NoSuchFieldException e) {
+ /* API level 3 doesn't have this field unfortunately. */
+ }
+ try {
+ methodGetUidForPid = Process.class.getMethod("getUidForPid", int.class);
+ } catch(NoSuchMethodException e) {
+ Log.w(TAG, "Could not access getUidForPid method");
+ }
+ try {
+ methodGetPids = Process.class.getMethod("getPids", String.class,
+ int[].class);
+ } catch(NoSuchMethodException e) {
+ Log.w(TAG, "Could not access getPids method");
+ }
+ try {
+ methodReadProcFile = Process.class.getMethod("readProcFile", String.class,
+ int[].class, String[].class, long[].class, float[].class);
+ } catch(NoSuchMethodException e) {
+ Log.w(TAG, "Could not access readProcFile method");
+ }
+ try {
+ Class classSystemProperties = Class.forName("android.os.SystemProperties");
+ methodGetProperty = classSystemProperties.getMethod("get", String.class);
+ } catch(NoSuchMethodException e) {
+ Log.w(TAG, "Could not access SystemProperties.get");
+ } catch(ClassNotFoundException e) {
+ Log.w(TAG, "Could not find class android.os.SystemProperties");
+ }
+ readBuf = new long[1];
+ }
+
+ public int getUidForPid(int pid) {
+ if(methodGetUidForPid != null) try {
+ return (Integer)methodGetUidForPid.invoke(null, pid);
+ } catch(InvocationTargetException e) {
+ Log.w(TAG, "Call to getUidForPid failed");
+ } catch(IllegalAccessException e) {
+ Log.w(TAG, "Call to getUidForPid failed");
+ } else try {
+ BufferedReader rdr = new BufferedReader(new InputStreamReader(
+ new FileInputStream("/proc/" + pid + "/status")), 256);
+ for(String line = rdr.readLine(); line != null; line = rdr.readLine()) {
+ if(line.startsWith("Uid:")) {
+ String tokens[] = line.substring(4).split("[ \t]+");
+ String realUidToken = tokens[tokens[0].length() == 0 ? 1 : 0];
+ try {
+ return Integer.parseInt(realUidToken);
+ } catch(NumberFormatException e) {
+ return -1;
+ }
+ }
+ }
+ } catch(IOException e) {
+ Log.w(TAG, "Failed to manually read in process uid");
+ }
+ return -1;
+ }
+
+ public int getUidForProcessInfo(
+ ActivityManager.RunningAppProcessInfo app) {
+ /* Try to access the uid field first if it is avaialble. Otherwise just
+ * convert the pid to a uid.
+ */
+ if(fieldUid != null) try {
+ return (Integer)fieldUid.get(app);
+ } catch(IllegalAccessException e) {
+ }
+ return getUidForPid(app.pid);
+ }
+
+ /* lastPids can be null. It is just used to avoid memory reallocation if
+ * at all possible. Returns null on failure. If lastPids can hold the new
+ * pid list the extra entries will be filled with -1 at the end.
+ */
+ public int[] getPids(int[] lastPids) {
+ if(methodGetPids == null) return manualGetInts("/proc", lastPids);
+ try {
+ return (int[])methodGetPids.invoke(null, "/proc", lastPids);
+ } catch(IllegalAccessException e) {
+ Log.w(TAG, "Failed to get process cpu usage");
+ } catch(InvocationTargetException e) {
+ Log.w(TAG, "Exception thrown while getting cpu usage");
+ }
+ return null;
+ }
+
+ /* Gets a property on Android accessible through getprop. */
+ public String getProperty(String property) {
+ if(methodGetProperty == null) return null;
+ try {
+ return (String)methodGetProperty.invoke(null, property);
+ } catch(IllegalAccessException e) {
+ Log.w(TAG, "Failed to get property");
+ } catch(InvocationTargetException e) {
+ Log.w(TAG, "Exception thrown while getting property");
+ }
+ return null;
+ }
+
+ /* lastUids can be null. It is just used to avoid memory reallocation if
+ * at all possible. Returns null on failure. If lastUids can hold the new
+ * uid list the extra entries will be filled with -1 at the end.
+ */
+ public int[] getUids(int[] lastUids) {
+ if(methodGetPids == null) return manualGetInts("/proc/uid_stat", lastUids);
+ try {
+ return (int[])methodGetPids.invoke(null, "/proc/uid_stat", lastUids);
+ } catch(IllegalAccessException e) {
+ Log.w(TAG, "Failed to get process cpu usage");
+ } catch(InvocationTargetException e) {
+ Log.w(TAG, "Exception thrown while getting cpu usage");
+ }
+ return null;
+ }
+
+ private int[] manualGetInts(String dir, int[] lastInts) {
+ File[] files = new File(dir).listFiles();
+ int sz = files == null ? 0 : files.length;
+ if(lastInts == null || lastInts.length < sz) {
+ lastInts = new int[sz];
+ } else if(2 * sz < lastInts.length) {
+ lastInts = new int[sz];
+ }
+ int pos = 0;
+ for(int i = 0; i < sz; i++) {
+ try {
+ int v = Integer.parseInt(files[i].getName());
+ lastInts[pos++] = v;
+ } catch(NumberFormatException e) {
+ }
+ }
+ while(pos < lastInts.length) lastInts[pos++] = -1;
+ return lastInts;
+ }
+
+ /* times should contain two elements. times[INDEX_USER_TIME] will be filled
+ * with the user time for this pid and times[INDEX_SYS_TIME] will be filled
+ * with the sys time for this pid. Returns true on sucess.
+ */
+ public boolean getPidUsrSysTime(int pid, long[] times) {
+ if(methodReadProcFile == null) return false;
+ try {
+ return (Boolean)methodReadProcFile.invoke(
+ null, "/proc/" + pid + "/stat",
+ PROCESS_STATS_FORMAT, null, times, null);
+ } catch(IllegalAccessException e) {
+ Log.w(TAG, "Failed to get pid cpu usage");
+ } catch(InvocationTargetException e) {
+ Log.w(TAG, "Exception thrown while getting pid cpu usage");
+ }
+ return false;
+ }
+
+ /* times should contain seven elements. times[INDEX_USER_TIME] will be filled
+ * with the total user time, times[INDEX_SYS_TIME] will be filled
+ * with the total sys time, and times[INDEX_TOTAL_TIME] will have the total
+ * time (including idle cycles). Returns true on success.
+ */
+ public boolean getUsrSysTotalTime(long[] times) {
+ if(methodReadProcFile == null) return false;
+ try {
+ if((Boolean)methodReadProcFile.invoke(
+ null, "/proc/stat",
+ PROCESS_TOTAL_STATS_FORMAT, null, times, null)) {
+ long usr = times[0] + times[1];
+ long sys = times[2] + times[5] + times[6];
+ long total = usr + sys + times[3] + times[4];
+ times[INDEX_USER_TIME] = usr;
+ times[INDEX_SYS_TIME] = sys;
+ times[INDEX_TOTAL_TIME] = total;
+ return true;
+ }
+ } catch(IllegalAccessException e) {
+ Log.w(TAG, "Failed to get total cpu usage");
+ } catch(InvocationTargetException e) {
+ Log.w(TAG, "Exception thrown while getting total cpu usage");
+ }
+ return false;
+ }
+
+ /* mem should contain 4 elements. mem[INDEX_MEM_TOTAL] will contain total
+ * memory available in kb, mem[INDEX_MEM_FREE] will give the amount of free
+ * memory in kb, mem[INDEX_MEM_BUFFERS] will give the size of kernel buffers
+ * in kb, and mem[INDEX_MEM_CACHED] will give the size of kernel caches in kb.
+ * Returns true on success.
+ */
+ public boolean getMemInfo(long[] mem) {
+ if(methodReadProcFile == null) return false;
+ try {
+ if((Boolean)methodReadProcFile.invoke(
+ null, "/proc/meminfo",
+ PROC_MEMINFO_FORMAT, null, mem, null)) {
+ return true;
+ }
+ } catch(IllegalAccessException e) {
+ Log.w(TAG, "Failed to get mem info");
+ } catch(InvocationTargetException e) {
+ Log.w(TAG, "Exception thrown while getting mem info");
+ }
+ return false;
+ }
+
+ /* Returns -1 on failure. */
+ public long readLongFromFile(String file) {
+ if(methodReadProcFile == null) return -1;
+ try {
+ if((Boolean)methodReadProcFile.invoke(
+ null, file, READ_LONG_FORMAT, null, readBuf, null)) {
+ return readBuf[0];
+ }
+ } catch(IllegalAccessException e) {
+ Log.w(TAG, "Failed to get pid cpu usage");
+ } catch(InvocationTargetException e) {
+ Log.w(TAG, "Exception thrown while getting pid cpu usage");
+ }
+ return -1L;
+ }
+
+ SparseArray<UidCacheEntry> uidCache = new SparseArray<UidCacheEntry>();
+
+ public synchronized String getAppId(int uid, PackageManager pm) {
+ UidCacheEntry cacheEntry = uidCache.get(uid);
+ if(cacheEntry == null) {
+ cacheEntry = new UidCacheEntry();
+ uidCache.put(uid, cacheEntry);
+ }
+ cacheEntry.clearIfExpired();
+ if(cacheEntry.getAppId() != null) {
+ return cacheEntry.getAppId();
+ }
+ String result = getAppIdNoCache(uid, pm);
+ cacheEntry.setAppId(result);
+ return result;
+ }
+
+ private String getAppIdNoCache(int uid, PackageManager pm) {
+ if(uid < SystemInfo.AID_APP) {
+ Log.e(TAG, "Only pass application uids to getAppId");
+ return null;
+ }
+ int versionCode = -1;
+ String[] packages = pm.getPackagesForUid(uid);
+ if(packages != null) for(String packageName : packages) {
+ try {
+ PackageInfo info = pm.getPackageInfo(packageName, 0);
+ versionCode = info.versionCode;
+ } catch(PackageManager.NameNotFoundException e) {
+ }
+ }
+ String name = pm.getNameForUid(uid);
+ name = name == null ? "none" : name;
+ return pm.getNameForUid(uid) + "@" + versionCode;
+ }
+
+ public synchronized String getUidName(int uid, PackageManager pm) {
+ UidCacheEntry cacheEntry = uidCache.get(uid);
+ if(cacheEntry == null) {
+ cacheEntry = new UidCacheEntry();
+ uidCache.put(uid, cacheEntry);
+ }
+ cacheEntry.clearIfExpired();
+ if(cacheEntry.getName() != null) {
+ return cacheEntry.getName();
+ }
+ String result = getUidNameNoCache(uid, pm);
+ cacheEntry.setName(result);
+ return result;
+ }
+
+ private String getUidNameNoCache(int uid, PackageManager pm) {
+ switch(uid) {
+ case AID_ROOT:
+ return "Kernel";
+ case AID_SYSTEM:
+ return "System";
+ case AID_RADIO:
+ return "Radio Subsystem";
+ case AID_BLUETOOTH:
+ return "Bluetooth Subsystem";
+ case AID_GRAPHICS:
+ return "Graphics Devices";
+ case AID_INPUT:
+ return "Input Devices";
+ case AID_AUDIO:
+ return "Audio Devices";
+ case AID_CAMERA:
+ return "Camera Devices"; case AID_LOG:
+ return "Log Devices";
+ case AID_COMPASS:
+ return "Compass Device (e.g. akmd)";
+ case AID_MOUNT:
+ return "Mount";
+ case AID_WIFI:
+ return "Wifi Subsystem";
+ case AID_ADB:
+ return "Android Debug Bridge";
+ case AID_INSTALL:
+ return "Install";
+ case AID_MEDIA:
+ return "Media Server";
+ case AID_DHCP:
+ return "DHCP Client";
+ case AID_SHELL:
+ return "Debug Shell";
+ case AID_CACHE:
+ return "Cache Access";
+ case AID_DIAG:
+ return "Diagnostics";
+ }
+ if(uid < AID_APP) {
+ return "sys_" + uid;
+ }
+
+ String[] packages = pm.getPackagesForUid(uid);
+ if(packages != null) for(String packageName : packages) {
+ try {
+ PackageInfo info = pm.getPackageInfo(packageName, 0);
+ CharSequence label = info.applicationInfo.loadLabel(pm);
+ if(label != null) {
+ return label.toString();
+ }
+ } catch(PackageManager.NameNotFoundException e) {
+ }
+ }
+ String uidName = pm.getNameForUid(uid);
+ if(uidName != null) {
+ return uidName;
+ }
+ return "app_" + uid;
+ }
+
+ public synchronized Drawable getUidIcon(int uid, PackageManager pm) {
+ UidCacheEntry cacheEntry = uidCache.get(uid);
+ if(cacheEntry == null) {
+ cacheEntry = new UidCacheEntry();
+ uidCache.put(uid, cacheEntry);
+ }
+ cacheEntry.clearIfExpired();
+ if(cacheEntry.getIcon() != null) {
+ return cacheEntry.getIcon();
+ }
+ Drawable result = getUidIconNoCache(uid, pm);
+ cacheEntry.setIcon(result);
+ return result;
+ }
+
+ public Drawable getUidIconNoCache(int uid, PackageManager pm) {
+ String[] packages = pm.getPackagesForUid(uid);
+ if(packages != null) for (int i = 0; i < packages.length; i++) {
+ try {
+ ApplicationInfo ai = pm.getApplicationInfo(packages[i], 0);
+ if(ai.icon != 0) {
+ return ai.loadIcon(pm);
+ }
+ } catch(PackageManager.NameNotFoundException e) {
+ }
+ }
+ return pm.getDefaultActivityIcon();
+ }
+
+ public synchronized void voidUidCache(int uid) {
+ uidCache.remove(uid);
+ }
+
+ private static class UidCacheEntry {
+ private static long EXPIRATION_TIME = 1000 * 60 * 10; // 10 minutes
+
+ private String appId;
+ private String name;
+ private Drawable icon;
+ private long updateTime;
+
+ public UidCacheEntry() {
+ updateTime = -1;
+ }
+
+ public String getAppId() {
+ return appId;
+ }
+
+ public void setAppId(String appId) {
+ this.appId = appId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ if(updateTime == -1) {
+ updateTime = SystemClock.elapsedRealtime();
+ }
+ }
+
+ public Drawable getIcon() {
+ return icon;
+ }
+
+ public void setIcon(Drawable icon) {
+ this.icon = icon;
+ if(updateTime == -1) {
+ updateTime = SystemClock.elapsedRealtime();
+ }
+ }
+
+ public void clearIfExpired() {
+ if(updateTime != -1 &&
+ updateTime + EXPIRATION_TIME < SystemClock.elapsedRealtime()) {
+ updateTime = -1;
+ name = null;
+ icon = null;
+ }
+ }
+ }
+}
+
diff --git a/src/edu/umich/PowerTutor/widget/Configure.java b/src/edu/umich/PowerTutor/widget/Configure.java
new file mode 100644
index 0000000..cf2046a
--- /dev/null
+++ b/src/edu/umich/PowerTutor/widget/Configure.java
@@ -0,0 +1,163 @@
+/*
+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.widget;
+
+import edu.umich.PowerTutor.R;
+import edu.umich.PowerTutor.util.HexEncode;
+
+import android.app.Activity;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+import java.io.IOException;
+
+public class Configure extends Activity {
+ private static final String TAG = "Configure";
+
+ private int widgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
+
+ private ArrayAdapter adapter;
+ private DataSource[] dataSource;
+ private WidgetItem[] items;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setResult(RESULT_CANCELED);
+
+ Intent intent = getIntent();
+ Bundle extras = intent.getExtras();
+ if(extras != null) {
+ widgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID);
+ }
+ if(widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ finish();
+ }
+
+ setContentView(R.layout.widget_configure);
+ findViewById(R.id.save_button).setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ String val = "";
+ try {
+ ByteArrayOutputStream ba = new ByteArrayOutputStream();
+ ObjectOutputStream objout = new ObjectOutputStream(ba);
+ for(int i = 0; i < dataSource.length; i++) {
+ objout.writeObject(dataSource[i]);
+ }
+ objout.close();
+ val = HexEncode.encode(ba.toByteArray());
+ } catch(IOException e) {
+ Log.w(TAG, "Failed to write data sources to string");
+ finish();
+ }
+ String key = "widget_" + widgetId;
+
+ SharedPreferences prefs =
+ PreferenceManager.getDefaultSharedPreferences(Configure.this);
+ prefs.edit().putString(key, val).commit();
+
+ Intent resultValue = new Intent();
+ resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
+ setResult(RESULT_OK, resultValue);
+ finish();
+ }
+ });
+ findViewById(R.id.cancel_button).setOnClickListener(
+ new View.OnClickListener() {
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ final ListView listView = (ListView)findViewById(R.id.list);
+ adapter = new ArrayAdapter(this, 0) {
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View itemView = getLayoutInflater()
+ .inflate(R.layout.widget_item_layout, listView, false);
+ TextView title = (TextView)itemView.findViewById(R.id.title);
+ TextView summary = (TextView)itemView.findViewById(R.id.summary);
+ WidgetItem item = (WidgetItem)getItem(position);
+ item.setupView(title, summary);
+ return itemView;
+ }
+ };
+
+ dataSource = DataSource.getDefaults();
+ items = new WidgetItem[3];
+
+ for(int i = 0; i < 3; i++) {
+ items[i] = new WidgetItem(i);
+ adapter.add(items[i]);
+ }
+ listView.setAdapter(adapter);
+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView parent, View view,
+ int position, long id) {
+ items[position].onClick();
+ }
+ });
+ }
+
+ @Override
+ protected void onActivityResult(int reqCode, int resCode, Intent data) {
+ if(resCode == RESULT_OK) {
+ Bundle extras = data.getExtras();
+ DataSource dataSrc = (DataSource)extras.getSerializable("data_source");
+ dataSource[reqCode] = dataSrc;
+ adapter.notifyDataSetChanged();
+ }
+ }
+
+ private class WidgetItem {
+ private int columnId;
+
+ public WidgetItem(int columnId) {
+ this.columnId = columnId;
+ }
+
+ public void setupView(TextView title, TextView summary) {
+ title.setText("Column " + (columnId + 1) + " - " +
+ dataSource[columnId].getTitle());
+ summary.setText(dataSource[columnId].getDescription());
+ }
+
+ public void onClick() {
+ Intent startIntent = new Intent(Configure.this,
+ DataSourceConfigure.class);
+ startActivityForResult(startIntent, columnId);
+ }
+ }
+}
diff --git a/src/edu/umich/PowerTutor/widget/DataSource.java b/src/edu/umich/PowerTutor/widget/DataSource.java
new file mode 100644
index 0000000..575845f
--- /dev/null
+++ b/src/edu/umich/PowerTutor/widget/DataSource.java
@@ -0,0 +1,341 @@
+/*
+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.widget;
+
+import java.io.Serializable;
+
+import edu.umich.PowerTutor.service.PowerEstimator;
+import edu.umich.PowerTutor.util.BatteryStats;
+import edu.umich.PowerTutor.util.Counter;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+public class DataSource implements Serializable {
+ private static final long serialVersionUID = 14594389587290845L;
+
+ public int id;
+ public int[] params;
+
+ private static final int ID_POWER = 0;
+ private static final int ID_BATTERY_TIME = 1;
+ private static final int ID_CHARGE = 2;
+ private static final int ID_VOLTAGE = 3;
+ private static final int ID_CURRENT = 4;
+ private static final int ID_TEMP = 5;
+ private static final int ID_PERC = 6;
+ private static final String[] idShorts = {
+ "Power",
+ "Battery Time",
+ "Charge",
+ "Voltage",
+ "Current",
+ "Temperature",
+ "Percent Battery",
+ };
+ private static final String[] idLongs = {
+ "Displays power in mW",
+ "Displays remaining battery lifetime. Also can show time until battery " +
+ "is fully charged",
+ "Displays remaining battery charge in mAh",
+ "Displays battery voltage in V",
+ "Displays battery current in mA",
+ "Displays battery temperature",
+ "Displays the percent battery remaning",
+ };
+
+ private static final int POWER_INSTANT = 0;
+ private static final int POWER_MINUTE = 1;
+ private static final int POWER_HOUR = 2;
+ private static final int POWER_DAY = 3;
+ private static final int POWER_TOTAL = 4;
+ private static final int POWER_SENSOR = 5;
+ private static final String[] powerShorts = {
+ "Instant",
+ "Minute Average",
+ "Hour Average",
+ "Day Average",
+ "Total Average",
+ "Battery Sensors",
+ };
+ private static final String[] powerLongs = {
+ "Estimated instantaneous power consumption",
+ "Average power consumption over the last minute",
+ "Average power consumption over the last hour",
+ "Average power consumption over the last day",
+ "Average power consumption while the profiler has been running",
+ "Calculate power from battery current and voltage sensors",
+ };
+
+ private static final int CHARGE_SENSOR = 0;
+ private static final int CHARGE_FULL = 1;
+ private static final int CHARGE_1400 = 2;
+ private static final String[] chargeShorts = {
+ "Battery Sensor",
+ "Fully Charged",
+ "1400 mAh",
+ };
+ private static final String[] chargeLongs = {
+ "Use your battery's charge readings",
+ "Assume that your battery is fully charged",
+ "Assume that you have 1400 mAh worth of charge " +
+ "(A typical battery capacity)",
+ };
+
+ private static final int TEMP_CELCIUS = 0;
+ private static final int TEMP_FARENHEIT = 1;
+ private static final String[] tempShorts = {
+ "Celcius",
+ "Farenheit",
+ };
+ private static final String[] tempLongs = {
+ "Display battery temperature in celcius",
+ "Display battery temperature in farenheit",
+ };
+
+ private static final int BATT_LIFETIME = 0;
+ private static final int BATT_CHARGETIME = 1;
+ private static final String[] battShorts = {
+ "Life Time",
+ "Charge Time",
+ };
+ private static final String[] battLongs = {
+ "Display remaining life time when battery plugged in",
+ "Display time until battery fully charged when battery plugged in",
+ };
+
+ public String getTitle(int level) {
+ if(level == 0) return "Select display type";
+ switch(id) {
+ case ID_POWER: return "Select power source";
+ case ID_BATTERY_TIME:
+ if(level == 1) return "Select power source";
+ if(level == 2) return "Select charge source";
+ return "Select charging behavior";
+ case ID_TEMP: return "Select temperature scale";
+ }
+ return "";
+ }
+
+ public String[] getShortOptions(int level) {
+ if(level == 0) return idShorts;
+ switch(id) {
+ case ID_POWER: return powerShorts;
+ case ID_BATTERY_TIME: return level == 1 ? powerShorts :
+ (level == 2 ? chargeShorts : battShorts);
+ case ID_TEMP: return tempShorts;
+ }
+ return null;
+ }
+
+ public String[] getLongOptions(int level) {
+ if(level == 0) return idLongs;
+ switch(id) {
+ case ID_POWER: return powerLongs;
+ case ID_BATTERY_TIME: return level == 1 ? powerLongs :
+ (level == 2 ? chargeLongs : battLongs);
+ case ID_TEMP: return tempLongs;
+ }
+ return null;
+ }
+
+ public boolean hasOption(int level, int value) {
+ BatteryStats bst = BatteryStats.getInstance();
+ if(level == 0) {
+ if(value == ID_PERC) return bst.hasCapacity();
+ if(value == ID_CHARGE) return bst.hasCharge();
+ if(value == ID_CURRENT) return bst.hasCurrent();
+ return true;
+ }
+ switch(id) {
+ case ID_POWER:
+ return value != POWER_SENSOR || bst.hasCurrent() && bst.hasVoltage();
+ case ID_BATTERY_TIME:
+ if(level == 1) {
+ return value != POWER_SENSOR || bst.hasCurrent() && bst.hasVoltage();
+ } else if(level == 2) {
+ return value == CHARGE_1400 ||
+ value == CHARGE_SENSOR && bst.hasCharge() ||
+ value == CHARGE_FULL && bst.hasFullCapacity();
+ }
+ return true;
+ case ID_TEMP:
+ return true;
+ }
+ return false;
+ }
+
+ public boolean setParam(int level, int value) {
+ if(level == 0) {
+ id = value;
+ int numParams = 0;
+ switch(id) {
+ case ID_POWER: numParams = 1; break;
+ case ID_BATTERY_TIME: numParams = 3; break;
+ case ID_TEMP: numParams = 1; break;
+ }
+ if(numParams > 0) {
+ params = new int[numParams];
+ }
+ return level == numParams;
+ } else {
+ params[level - 1] = value;
+ if(id == ID_BATTERY_TIME) {
+ BatteryStats bst = BatteryStats.getInstance();
+ if(!(bst.hasCurrent() && bst.hasCharge() && bst.hasCapacity())) {
+ return level + 1 == params.length;
+ }
+ }
+ return level == params.length;
+ }
+ }
+
+ public String getTitle() {
+ return idShorts[id];
+ }
+
+ public String getDescription() {
+ switch(id) {
+ case ID_POWER: return powerLongs[params[0]];
+ case ID_BATTERY_TIME:
+ return "Power Source: " + powerLongs[params[0]] + "\n" +
+ "Charge Source: " + chargeLongs[params[1]];
+ case ID_TEMP: return tempLongs[params[0]];
+ }
+ return idLongs[id];
+ }
+
+ public static DataSource[] getDefaults() {
+ DataSource[] res = new DataSource[3];
+ for(int i = 0; i < res.length; i++) res[i] = new DataSource();
+ res[0].setParam(0, ID_POWER);
+ res[0].setParam(1, POWER_TOTAL);
+ res[1].setParam(0, ID_PERC);
+ res[2].setParam(0, ID_TEMP);
+ res[2].setParam(1, TEMP_CELCIUS);
+ return res;
+ }
+
+ public String getValue(PowerEstimator p) {
+ BatteryStats bst = BatteryStats.getInstance();
+ switch(id) {
+ case ID_POWER: {
+ double pow = calcPower(p, params[0]);
+ if(pow <= 0) {
+ return "Power\n-";
+ }
+ return String.format("Power\n%1$.0f mW", 1000 * pow);
+ } case ID_BATTERY_TIME: {
+ if(bst.hasCurrent() && params[2] == BATT_CHARGETIME) {
+ double curr = bst.getCurrent();
+ double cp = bst.getCapacity();
+ if(curr > 0 && cp >= 0.01) {
+ // We have been asked to compute the charge time instead.
+ long time = (long)(bst.getCharge() / cp * (1.0 - cp) / curr);
+ return String.format("Charge time\n%1$d:%2$02d:%3$02d",
+ time / 60 / 60, time / 60 % 60, time % 60);
+ }
+ }
+
+ double pow = calcPower(p, params[0]);
+ double charge = calcCharge(params[1]);
+ double volt = bst.getVoltage();
+ if(pow <= 0 || charge <= 0 || volt <= 0) {
+ return "Batt. time\n-";
+ }
+ long time = (long)(charge * volt / pow);
+ return String.format("Batt. time\n%1$d:%2$02d:%3$02d",
+ time / 60 / 60, time / 60 % 60, time % 60);
+ } case ID_CHARGE: {
+ return String.format("Charge\n%1$.1f mAh",
+ calcCharge(CHARGE_SENSOR) / 3.6);
+ } case ID_VOLTAGE: {
+ return String.format("Voltage\n%1$.2f V", bst.getVoltage());
+ } case ID_CURRENT: {
+ double curr = bst.getCurrent() * 1000;
+ if(curr < 0) {
+ return String.format("Current\n%1$.1f mA", -curr);
+ } else {
+ return String.format("Current\n%1$.1f mA\n(charging)", curr);
+ }
+ } case ID_TEMP: {
+ if(params[0] == TEMP_FARENHEIT) {
+ return String.format("Temp.\n%1$.1f \u00b0F",
+ bst.getTemp() * 9 / 5 + 32);
+ } else {
+ return String.format("Temp.\n%1$.1f \u00b0C", bst.getTemp());
+ }
+ } case ID_PERC: {
+ return String.format("Batt. left\n%1$.0f%%", 100 * bst.getCapacity());
+ }
+ }
+ return "";
+ }
+
+ private static final double POLY_WEIGHT = 0.02;
+
+ private double calcPower(PowerEstimator p, int powId) {
+ switch(powId) {
+ case POWER_INSTANT: {
+ int count = 0;
+ int[] history = p.getComponentHistory(5 * 60, -1,
+ SystemInfo.AID_ALL, -1);
+ double weightedAvgPower = 0;
+ for(int i = history.length - 1; i >= 0; i--) {
+ if(history[i] != 0) {
+ count++;
+ weightedAvgPower *= 1.0 - POLY_WEIGHT;
+ weightedAvgPower += POLY_WEIGHT * history[i] / 1000.0;
+ }
+ }
+ if(count == 0) return -1.0;
+ return weightedAvgPower / (1.0 - Math.pow(1.0 - POLY_WEIGHT, count));
+ } case POWER_MINUTE:
+ case POWER_HOUR:
+ case POWER_DAY:
+ case POWER_TOTAL: {
+ int wind = 0;
+ if(powId == POWER_MINUTE) wind = Counter.WINDOW_MINUTE;
+ if(powId == POWER_HOUR) wind = Counter.WINDOW_HOUR;
+ if(powId == POWER_DAY) wind = Counter.WINDOW_DAY;
+ if(powId == POWER_TOTAL) wind = Counter.WINDOW_TOTAL;
+ double total = 0;
+ for(long x : p.getMeans(SystemInfo.AID_ALL, wind)) {
+ total += x / 1000.0;
+ }
+ return total;
+ } case POWER_SENSOR: {
+ BatteryStats bst = BatteryStats.getInstance();
+ double curr = bst.getCurrent();
+ if(curr >= 0) return -1.0;
+ return curr * bst.getVoltage();
+ }
+ }
+ return -1.0;
+ }
+
+ private double calcCharge(int chargeId) {
+ BatteryStats bst = BatteryStats.getInstance();
+ switch(chargeId) {
+ case CHARGE_SENSOR: return bst.getCharge();
+ case CHARGE_FULL: return bst.getFullCapacity();
+ case CHARGE_1400: return 1400 * 3.6; // mAh -> As
+ }
+ return -1.0;
+ }
+}
diff --git a/src/edu/umich/PowerTutor/widget/DataSourceConfigure.java b/src/edu/umich/PowerTutor/widget/DataSourceConfigure.java
new file mode 100644
index 0000000..e056d4c
--- /dev/null
+++ b/src/edu/umich/PowerTutor/widget/DataSourceConfigure.java
@@ -0,0 +1,134 @@
+/*
+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.widget;
+
+import edu.umich.PowerTutor.R;
+
+import android.app.Activity;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.TextView;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+
+public class DataSourceConfigure extends Activity {
+ private DataSource dataSource;
+ private int level;
+
+ private String[] shortOptions;
+ private String[] longOptions;
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setResult(RESULT_CANCELED);
+
+ Intent intent = getIntent();
+ Bundle extras = intent.getExtras();
+ if(extras != null) {
+ dataSource = (DataSource)extras.getSerializable("data_source");
+ level = extras.getInt("level");
+ }
+ if(dataSource == null) {
+ dataSource = new DataSource();
+ level = 0;
+ }
+ setTitle(dataSource.getTitle(level));
+ shortOptions = dataSource.getShortOptions(level);
+ longOptions = dataSource.getLongOptions(level);
+
+ final ListView listView = new ListView(this);
+ ArrayAdapter adapter = new ArrayAdapter(this, 0) {
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View itemView = getLayoutInflater()
+ .inflate(R.layout.widget_item_layout, listView, false);
+ TextView title = (TextView)itemView.findViewById(R.id.title);
+ TextView summary = (TextView)itemView.findViewById(R.id.summary);
+ Item item = (Item)getItem(position);
+ item.setupView(title, summary);
+ return itemView;
+ }
+ };
+
+ int pos = 0;
+ final Item[] items = new Item[shortOptions.length];
+ for(int i = 0; i < shortOptions.length; i++) {
+ if(dataSource.hasOption(level, i)) {
+ items[pos] = new Item(i);
+ adapter.add(items[pos++]);
+ }
+ }
+ listView.setAdapter(adapter);
+ listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ public void onItemClick(AdapterView parent, View view,
+ int position, long id) {
+ items[position].onClick();
+ }
+ });
+ setContentView(listView);
+ }
+
+ @Override
+ protected void onActivityResult(int reqCode, int resCode, Intent data) {
+ if(resCode == RESULT_OK) {
+ Intent resultValue = new Intent();
+ resultValue.putExtras(data);
+ setResult(RESULT_OK, resultValue);
+ finish();
+ }
+ }
+
+ private class Item {
+ private int id;
+
+ public Item(int id) {
+ this.id = id;
+ }
+
+ public void setupView(TextView title, TextView summary) {
+ title.setText(shortOptions[id]);
+ summary.setText(longOptions[id]);
+ }
+
+ public void onClick() {
+ if(dataSource.setParam(level, id)) {
+ Intent resultValue = new Intent();
+ resultValue.putExtra("data_source", dataSource);
+ setResult(RESULT_OK, resultValue);
+ finish();
+ } else {
+ Intent startIntent = new Intent(DataSourceConfigure.this,
+ DataSourceConfigure.class);
+ startIntent.putExtra("data_source", dataSource);
+ startIntent.putExtra("level", level + 1);
+ startActivityForResult(startIntent, 0);
+ }
+ }
+ }
+}
diff --git a/src/edu/umich/PowerTutor/widget/PowerWidget.java b/src/edu/umich/PowerTutor/widget/PowerWidget.java
new file mode 100644
index 0000000..c19a042
--- /dev/null
+++ b/src/edu/umich/PowerTutor/widget/PowerWidget.java
@@ -0,0 +1,152 @@
+/*
+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.widget;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetProvider;
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.widget.RemoteViews;
+
+import edu.umich.PowerTutor.R;
+import edu.umich.PowerTutor.service.PowerEstimator;
+import edu.umich.PowerTutor.service.UMLoggerService;
+import edu.umich.PowerTutor.ui.UMLogger;
+import edu.umich.PowerTutor.util.Counter;
+import edu.umich.PowerTutor.util.HexEncode;
+import edu.umich.PowerTutor.util.SystemInfo;
+
+import java.io.ByteArrayInputStream;
+import java.io.ObjectInputStream;
+import java.io.IOException;
+
+public class PowerWidget extends AppWidgetProvider {
+ private static final String TAG = "PowerWidget";
+
+ private static final int[] text_ids = {
+ R.id.text_minute,
+ R.id.text_hour,
+ R.id.text_day,
+ };
+
+ private static long sumArray(long[] A) {
+ long ret = 0;
+ for(long x : A) {
+ ret += x;
+ }
+ return ret;
+ }
+
+ // Called once initially.
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager,
+ int[] appWidgetIds) {
+ updateWidgetDone(context);
+ }
+
+ public void onDeleted(Context context, int[] appWidgetIds) {
+ SharedPreferences.Editor edit =
+ PreferenceManager.getDefaultSharedPreferences(context).edit();
+ for(int id : appWidgetIds) {
+ edit.remove("widget_" + id);
+ }
+ edit.commit();
+ }
+
+ public static void updateWidgetDone(Context context) {
+ AppWidgetManager manager = AppWidgetManager.getInstance(context);
+ ComponentName comp = new ComponentName(context, PowerWidget.class);
+ RemoteViews views = new RemoteViews(context.getPackageName(),
+ R.layout.widget_layout);
+
+ Intent notificationIntent = new Intent(context, UMLogger.class);
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
+ notificationIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ views.setOnClickPendingIntent(R.id.power_button, pendingIntent);
+ views.setInt(R.id.power_button, "setImageResource",
+ R.drawable.power_off);
+ for(int i = 0; i < text_ids.length; i++) {
+ views.setTextViewText(text_ids[i], "N/A");
+ }
+ manager.updateAppWidget(comp, views);
+ }
+
+ // Called by the UMLogger Service every so often.
+ public static void updateWidget(Context context, PowerEstimator p) {
+ AppWidgetManager manager = AppWidgetManager.getInstance(context);
+ ComponentName comp = new ComponentName(context, PowerWidget.class);
+
+ for(int id : manager.getAppWidgetIds(comp)) {
+ RemoteViews views = new RemoteViews(context.getPackageName(),
+ R.layout.widget_layout);
+
+ Intent notificationIntent = new Intent(context, UMLogger.class);
+ notificationIntent.putExtra("isFromIcon", true);
+ PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
+ notificationIntent,
+ PendingIntent.FLAG_UPDATE_CURRENT);
+
+ views.setOnClickPendingIntent(R.id.power_button, pendingIntent);
+ views.setInt(R.id.power_button, "setImageResource",
+ R.drawable.power_on);
+
+ boolean ok = false;
+ try {
+ SharedPreferences prefs =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ String key = "widget_" + id;
+ String val = prefs.getString(key, null);
+ if(val != null) {
+ ObjectInputStream objin = new ObjectInputStream(
+ new ByteArrayInputStream(HexEncode.decode(val)));
+ for(int i = 0; i < text_ids.length; i++) {
+ DataSource dataSource = (DataSource)objin.readObject();
+ views.setTextViewText(text_ids[i], dataSource.getValue(p));
+ }
+ ok = true;
+ } else {
+ Log.w(TAG, "Could not find widget data source preference");
+ }
+ } catch(IOException e) {
+ Log.w(TAG, "Failed to extract widget data sources");
+ } catch(ClassCastException e) {
+ Log.w(TAG, "Failed to extract widget data sources");
+ } catch(ClassNotFoundException e) {
+ Log.w(TAG, "Failed to extract widget data sources");
+ }
+ if(!ok) {
+ for(int i =0; i < text_ids.length; i++) {
+ views.setTextViewText(text_ids[i], "N/A");
+ }
+ }
+ for(int i =0; i < text_ids.length; i++) {
+ views.setTextColor(text_ids[i], 0xFFFFFFFF);
+ }
+
+ manager.updateAppWidget(id, views);
+ }
+ }
+}