/* 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"); } } }