Initial rough stab at a proc stats UI.

You see procs, and you see some stats.

Change-Id: I91c86d198fd5e64a41c72901e150466b3c602caf
diff --git a/src/com/android/settings/DevelopmentSettings.java b/src/com/android/settings/DevelopmentSettings.java
index 66f024e..11ffbf7 100644
--- a/src/com/android/settings/DevelopmentSettings.java
+++ b/src/com/android/settings/DevelopmentSettings.java
@@ -1283,6 +1283,8 @@
             writeDebugLayoutOptions();
         } else if (preference == mForceRtlLayout) {
             writeForceRtlOptions();
+        } else {
+            return super.onPreferenceTreeClick(preferenceScreen, preference);
         }
 
         return false;
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index f5dbd3b..a9d35a2 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -64,6 +64,7 @@
 import com.android.settings.accounts.ManageAccountsSettings;
 import com.android.settings.applications.AppOpsSummary;
 import com.android.settings.applications.ManageApplications;
+import com.android.settings.applications.ProcessStatsUi;
 import com.android.settings.bluetooth.BluetoothEnabler;
 import com.android.settings.bluetooth.BluetoothSettings;
 import com.android.settings.deviceinfo.Memory;
@@ -310,6 +311,7 @@
         DisplaySettings.class.getName(),
         DeviceInfoSettings.class.getName(),
         ManageApplications.class.getName(),
+        ProcessStatsUi.class.getName(),
         NotificationStation.class.getName(),
         AppOpsSummary.class.getName(),
         LocationSettings.class.getName(),
@@ -331,7 +333,7 @@
         UserSettings.class.getName(),
         NotificationAccessSettings.class.getName(),
         ManageAccountsSettings.class.getName(),
-        PrintingSettings.class.getName()
+        PrintingSettings.class.getName(),
     };
 
     @Override
diff --git a/src/com/android/settings/applications/ProcessStatsPreference.java b/src/com/android/settings/applications/ProcessStatsPreference.java
new file mode 100644
index 0000000..1b9e852
--- /dev/null
+++ b/src/com/android/settings/applications/ProcessStatsPreference.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.content.Context;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.preference.Preference;
+import android.text.format.Formatter;
+import android.view.View;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import com.android.settings.R;
+
+public class ProcessStatsPreference extends Preference {
+    private int mProgress;
+    private CharSequence mProgressText;
+
+    public ProcessStatsPreference(Context context, Drawable icon) {
+        super(context);
+        setLayoutResource(R.layout.app_percentage_item);
+        setIcon(icon != null ? icon : new ColorDrawable(0));
+    }
+
+    public void setPercent(double percentOfTotal, long pss) {
+        mProgress = (int) Math.ceil(percentOfTotal);
+        mProgressText = pss > 0 ? Formatter.formatShortFileSize(getContext(), pss) : "";
+        notifyChanged();
+    }
+
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+
+        final ProgressBar progress = (ProgressBar) view.findViewById(android.R.id.progress);
+        progress.setProgress(mProgress);
+
+        final TextView text1 = (TextView) view.findViewById(android.R.id.text1);
+        text1.setText(mProgressText);
+    }
+}
diff --git a/src/com/android/settings/applications/ProcessStatsUi.java b/src/com/android/settings/applications/ProcessStatsUi.java
new file mode 100644
index 0000000..6434e9e
--- /dev/null
+++ b/src/com/android/settings/applications/ProcessStatsUi.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserManager;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import com.android.internal.app.IProcessStats;
+import com.android.internal.app.ProcessStats;
+import com.android.settings.R;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class ProcessStatsUi extends PreferenceFragment {
+    private static final String TAG = "ProcessStatsUi";
+    private static final boolean DEBUG = false;
+
+    private static final String KEY_APP_LIST = "app_list";
+    private static final String KEY_MEM_STATUS = "mem_status";
+
+    private static final int MENU_STATS_REFRESH = Menu.FIRST;
+    private static final int MENU_HELP = Menu.FIRST + 2;
+
+    static final int MAX_ITEMS_TO_LIST = 20;
+
+    private static ProcessStats sStatsXfer;
+
+    IProcessStats mProcessStats;
+    UserManager mUm;
+    ProcessStats mStats;
+
+    private PreferenceGroup mAppListGroup;
+    private Preference mMemStatusPref;
+
+    long mTotalTime;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        if (icicle != null) {
+            mStats = sStatsXfer;
+        }
+
+        addPreferencesFromResource(R.xml.process_stats_summary);
+        mProcessStats = IProcessStats.Stub.asInterface(
+                ServiceManager.getService(ProcessStats.SERVICE_NAME));
+        mUm = (UserManager)getActivity().getSystemService(Context.USER_SERVICE);
+        mAppListGroup = (PreferenceGroup) findPreference(KEY_APP_LIST);
+        mMemStatusPref = mAppListGroup.findPreference(KEY_MEM_STATUS);
+        setHasOptionsMenu(true);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        refreshStats();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (getActivity().isChangingConfigurations()) {
+            sStatsXfer = mStats;
+        }
+    }
+
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        if (!(preference instanceof ProcessStatsPreference)) {
+            return false;
+        }
+
+        /*
+        PreferenceActivity pa = (PreferenceActivity)getActivity();
+        pa.startPreferencePanel(PowerUsageDetail.class.getName(), args,
+                R.string.details_title, null, null, 0);
+        */
+
+        return super.onPreferenceTreeClick(preferenceScreen, preference);
+    }
+
+    @Override
+    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+        MenuItem refresh = menu.add(0, MENU_STATS_REFRESH, 0, R.string.menu_stats_refresh)
+                .setIcon(R.drawable.ic_menu_refresh_holo_dark)
+                .setAlphabeticShortcut('r');
+        refresh.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM |
+                MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+
+        /*
+        String helpUrl;
+        if (!TextUtils.isEmpty(helpUrl = getResources().getString(R.string.help_url_battery))) {
+            final MenuItem help = menu.add(0, MENU_HELP, 0, R.string.help_label);
+            HelpUtils.prepareHelpMenuItem(getActivity(), help, helpUrl);
+        }
+        */
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case MENU_STATS_REFRESH:
+                mStats = null;
+                refreshStats();
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private void addNotAvailableMessage() {
+        Preference notAvailable = new Preference(getActivity());
+        notAvailable.setTitle(R.string.power_usage_not_available);
+        mAppListGroup.addPreference(notAvailable);
+    }
+
+    private void refreshStats() {
+        if (mStats == null) {
+            load();
+        }
+
+        mAppListGroup.removeAll();
+        mAppListGroup.setOrderingAsAdded(false);
+
+        mMemStatusPref.setOrder(-2);
+        mAppListGroup.addPreference(mMemStatusPref);
+        /*
+        BatteryHistoryPreference hist = new BatteryHistoryPreference(getActivity(), mStats);
+        hist.setOrder(-1);
+        mAppListGroup.addPreference(hist);
+        */
+
+        ProcessStats.ProcessDataCollection totals = new ProcessStats.ProcessDataCollection(
+                ProcessStats.ALL_SCREEN_ADJ, ProcessStats.ALL_MEM_ADJ,
+                ProcessStats.BACKGROUND_PROC_STATES);
+
+        long now = SystemClock.uptimeMillis();
+
+        mTotalTime = ProcessStats.dumpSingleTime(null, null, mStats.mMemFactorDurations,
+                mStats.mMemFactor, mStats.mStartTime, now);
+
+        ArrayList<ProcessStats.ProcessState> procs = mStats.collectProcessesLocked(
+                ProcessStats.ALL_SCREEN_ADJ, ProcessStats.ALL_MEM_ADJ,
+                ProcessStats.BACKGROUND_PROC_STATES, now, null);
+
+        final PackageManager pm = getActivity().getPackageManager();
+
+        for (int i=0, N=(procs != null ? procs.size() : 0); i<N; i++) {
+            ProcessStats.ProcessState ps = procs.get(i);
+            final double percentOfTotal = (((double)ps.mTmpTotalTime) / mTotalTime) * 100;
+            if (percentOfTotal < 1) continue;
+            ProcessStatsPreference pref = new ProcessStatsPreference(getActivity(), null);
+            ApplicationInfo targetApp = null;
+            String label = ps.mName;
+            if (ps.mCommonProcess == ps) {
+                // Only one app associated with this process.
+                try {
+                    targetApp = pm.getApplicationInfo(ps.mPackage,
+                            PackageManager.GET_DISABLED_COMPONENTS |
+                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
+                            PackageManager.GET_UNINSTALLED_PACKAGES);
+                    String name = targetApp.loadLabel(pm).toString();
+                    if (ps.mName.equals(ps.mPackage)) {
+                        label = name;
+                    } else {
+                        if (ps.mName.startsWith(ps.mPackage)) {
+                            int off = ps.mPackage.length();
+                            if (ps.mName.length() > off) {
+                                off++;
+                            }
+                            label = name + " (" + ps.mName.substring(off) + ")";
+                        } else {
+                            label = name + " (" + ps.mName + ")";
+                        }
+                    }
+                } catch (PackageManager.NameNotFoundException e) {
+                }
+            }
+            if (targetApp == null) {
+                String[] packages = pm.getPackagesForUid(ps.mUid);
+                for (String pkgName : packages) {
+                    try {
+                        final PackageInfo pi = pm.getPackageInfo(pkgName,
+                                PackageManager.GET_DISABLED_COMPONENTS |
+                                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
+                                PackageManager.GET_UNINSTALLED_PACKAGES);
+                        if (pi.sharedUserLabel != 0) {
+                            targetApp = pi.applicationInfo;
+                            final CharSequence nm = pm.getText(pkgName,
+                                    pi.sharedUserLabel, pi.applicationInfo);
+                            if (nm != null) {
+                                label = nm.toString() + " (" + ps.mName + ")";
+                            } else {
+                                label = targetApp.loadLabel(pm).toString() + " (" + ps.mName + ")";
+                            }
+                            break;
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                    }
+                }
+            }
+            pref.setTitle(label);
+            if (targetApp != null) {
+                pref.setIcon(targetApp.loadIcon(pm));
+            }
+            pref.setOrder(N+100-i);
+            ProcessStats.computeProcessData(ps, totals, now);
+            pref.setPercent(percentOfTotal, totals.avgPss * 1024);
+            mAppListGroup.addPreference(pref);
+            if (mAppListGroup.getPreferenceCount() > (MAX_ITEMS_TO_LIST+1)) break;
+        }
+    }
+
+    private void load() {
+        try {
+            ArrayList<ParcelFileDescriptor> fds = new ArrayList<ParcelFileDescriptor>();
+            byte[] data = mProcessStats.getCurrentStats(fds);
+            for (int i=0; i<fds.size(); i++) {
+                try {
+                    fds.get(i).close();
+                } catch (IOException e) {
+                }
+            }
+            Parcel parcel = Parcel.obtain();
+            parcel.unmarshall(data, 0, data.length);
+            parcel.setDataPosition(0);
+            mStats = ProcessStats.CREATOR.createFromParcel(parcel);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException:", e);
+        }
+    }
+}