aboutsummaryrefslogtreecommitdiff
path: root/src/edu/umich/PowerTutor/ui/PowerTop.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/edu/umich/PowerTutor/ui/PowerTop.java')
-rw-r--r--src/edu/umich/PowerTutor/ui/PowerTop.java428
1 files changed, 428 insertions, 0 deletions
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");
+ }
+ }
+}
+