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