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