Add details screen to proc stats.

Lets you see the actual RAM use and % running time, a list of
all of the services that are associated with it (kind-of, not
working correctly for those that share processes), and most
importantly a glorious FORCE STOP button.

Change-Id: I34ac30c88f1187227a8a7809ae103118c8b9a865
diff --git a/res/layout/process_stats_details.xml b/res/layout/process_stats_details.xml
new file mode 100644
index 0000000..0de1054
--- /dev/null
+++ b/res/layout/process_stats_details.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<ScrollView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:clipToPadding="false"
+    android:scrollbarStyle="@integer/preference_scrollbar_style">
+
+    <LinearLayout
+        android:id="@+id/all_details"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingTop="5dip"
+        android:paddingBottom="5dip"
+        android:orientation="vertical">
+
+        <include layout="@layout/app_percentage_item" />
+
+        <!-- Force stop and report buttons -->
+        <LinearLayout
+            android:id="@+id/two_buttons_panel"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="6dip"
+            android:orientation="vertical">
+            <include
+                layout="@layout/two_buttons_panel"/>
+        </LinearLayout>
+
+        <TextView
+            style="?android:attr/listSeparatorTextViewStyle"
+            android:text="@string/details_subtitle" />
+
+        <LinearLayout
+            android:id="@+id/details"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingStart="6dip"
+            android:orientation="vertical">
+
+            <!-- Insert detail items here -->
+
+        </LinearLayout>
+
+        <TextView
+            style="?android:attr/listSeparatorTextViewStyle"
+            android:text="@string/services_subtitle" />
+
+        <LinearLayout
+            android:id="@+id/services"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingStart="6dip"
+            android:orientation="vertical">
+
+            <!-- Insert service items here -->
+
+        </LinearLayout>
+
+    </LinearLayout>
+</ScrollView>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index faa274c..f309c8b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -3604,6 +3604,12 @@
     <!-- [CHAR LIMIT=NONE] Label for process stats, duration of time the stats are over -->
     <string name="process_stats_memory_status">Device memory is currently
         <xliff:g id="memstate">%1$s</xliff:g></string>
+    <!-- [CHAR LIMIT=NONE] Label for item showing details of average RAM use -->
+    <string name="process_stats_ram_use">Average RAM use</string>
+    <!-- [CHAR LIMIT=NONE] Label for item showing percent of time spent running -->
+    <string name="process_stats_run_time">Run time</string>
+    <!-- [CHAR LIMIT=NONE] Subtitle for process stats services list -->
+    <string name="services_subtitle">Services</string>
 
     <!-- Voice input/output settings --><skip />
     <!-- Title of setting on main settings screen. This item will take the user to the screen to tweak settings related to speech functionality -->
diff --git a/src/com/android/settings/applications/ProcStatsEntry.java b/src/com/android/settings/applications/ProcStatsEntry.java
index e180205..cbbfbb7 100644
--- a/src/com/android/settings/applications/ProcStatsEntry.java
+++ b/src/com/android/settings/applications/ProcStatsEntry.java
@@ -16,11 +16,22 @@
 
 package com.android.settings.applications;
 
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+import android.util.SparseArray;
 import com.android.internal.app.ProcessStats;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 
-public final class ProcStatsEntry {
+public final class ProcStatsEntry implements Parcelable {
+    private static final String TAG = "ProcStatsEntry";
+
     final String mPackage;
     final int mUid;
     final String mName;
@@ -29,7 +40,14 @@
     final long mAvgPss;
     final long mWeight;
 
-    ArrayList<Service> mServices;
+    String mBestTargetPackage;
+
+    ArrayList<Service> mServices = new ArrayList<Service>(2);
+
+    public ApplicationInfo mUiTargetApp;
+    public String mUiLabel;
+    public String mUiBaseLabel;
+    public String mUiPackage;
 
     public ProcStatsEntry(ProcessStats.ProcessState proc,
             ProcessStats.ProcessDataCollection tmpTotals) {
@@ -43,18 +61,156 @@
         mWeight = mDuration * mAvgPss;
     }
 
+    public ProcStatsEntry(Parcel in) {
+        mPackage = in.readString();
+        mUid = in.readInt();
+        mName = in.readString();
+        mUnique = in.readInt() != 0;
+        mDuration = in.readLong();
+        mAvgPss = in.readLong();
+        mWeight = in.readLong();
+        mBestTargetPackage = in.readString();
+        in.readTypedList(mServices, Service.CREATOR);
+    }
+
+    public void evaluateTargetPackage(ProcessStats stats,
+            ProcessStats.ProcessDataCollection totals, Comparator<ProcStatsEntry> compare) {
+        mBestTargetPackage = null;
+        if (mUnique) {
+            mBestTargetPackage = mPackage;
+            addServices(stats.getPackageStateLocked(mPackage, mUid));
+        } else {
+            // See if there is one significant package that was running here.
+            ArrayList<ProcStatsEntry> subProcs = new ArrayList<ProcStatsEntry>();
+            for (int ipkg=0, NPKG=stats.mPackages.getMap().size(); ipkg<NPKG; ipkg++) {
+                SparseArray<ProcessStats.PackageState> uids
+                        = stats.mPackages.getMap().valueAt(ipkg);
+                for (int iu=0, NU=uids.size(); iu<NU; iu++) {
+                    if (uids.keyAt(iu) != mUid) {
+                        continue;
+                    }
+                    ProcessStats.PackageState pkgState = uids.valueAt(iu);
+                    boolean match = false;
+                    for (int iproc=0, NPROC=pkgState.mProcesses.size(); iproc<NPROC; iproc++) {
+                        ProcessStats.ProcessState subProc =
+                                pkgState.mProcesses.valueAt(iproc);
+                        if (subProc.mName.equals(mName)) {
+                            match = true;
+                            subProcs.add(new ProcStatsEntry(subProc, totals));
+                        }
+                    }
+                    if (match) {
+                        addServices(stats.getPackageStateLocked(mPackage, mUid));
+                    }
+                }
+            }
+            if (subProcs.size() > 1) {
+                Collections.sort(subProcs, compare);
+                if (subProcs.get(0).mWeight > (subProcs.get(1).mWeight*3)) {
+                    mBestTargetPackage = subProcs.get(0).mPackage;
+                }
+            }
+        }
+    }
+
+    public void retrieveUiData(PackageManager pm) {
+        mUiTargetApp = null;
+        mUiLabel = mUiBaseLabel = mName;
+        mUiPackage = mBestTargetPackage;
+        if (mUiPackage != null) {
+            // Only one app associated with this process.
+            try {
+                mUiTargetApp = pm.getApplicationInfo(mUiPackage,
+                        PackageManager.GET_DISABLED_COMPONENTS |
+                        PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
+                        PackageManager.GET_UNINSTALLED_PACKAGES);
+                String name = mUiBaseLabel = mUiTargetApp.loadLabel(pm).toString();
+                if (mName.equals(mUiPackage)) {
+                    mUiLabel = name;
+                } else {
+                    if (mName.startsWith(mUiPackage)) {
+                        int off = mUiPackage.length();
+                        if (mName.length() > off) {
+                            off++;
+                        }
+                        mUiLabel = name + " (" + mName.substring(off) + ")";
+                    } else {
+                        mUiLabel = name + " (" + mName + ")";
+                    }
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+            }
+        }
+        if (mUiTargetApp == null) {
+            String[] packages = pm.getPackagesForUid(mUid);
+            if (packages != null) {
+                for (String curPkg : packages) {
+                    try {
+                        final PackageInfo pi = pm.getPackageInfo(curPkg,
+                                PackageManager.GET_DISABLED_COMPONENTS |
+                                PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
+                                PackageManager.GET_UNINSTALLED_PACKAGES);
+                        if (pi.sharedUserLabel != 0) {
+                            mUiTargetApp = pi.applicationInfo;
+                            final CharSequence nm = pm.getText(curPkg,
+                                    pi.sharedUserLabel, pi.applicationInfo);
+                            if (nm != null) {
+                                mUiBaseLabel = nm.toString();
+                                mUiLabel = mUiBaseLabel + " (" + mName + ")";
+                            } else {
+                                mUiBaseLabel = mUiTargetApp.loadLabel(pm).toString();
+                                mUiLabel = mUiBaseLabel + " (" + mName + ")";
+                            }
+                            break;
+                        }
+                    } catch (PackageManager.NameNotFoundException e) {
+                    }
+                }
+            } else {
+                // no current packages for this uid, typically because of uninstall
+                Log.i(TAG, "No package for uid " + mUid);
+            }
+        }
+    }
+
     public void addServices(ProcessStats.PackageState pkgState) {
         for (int isvc=0, NSVC=pkgState.mServices.size(); isvc<NSVC; isvc++) {
             ProcessStats.ServiceState svc = pkgState.mServices.valueAt(isvc);
             // XXX can't tell what process it is in!
-            if (mServices == null) {
-                mServices = new ArrayList<Service>();
-            }
             mServices.add(new Service(svc));
         }
     }
 
-    public static final class Service {
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mPackage);
+        dest.writeInt(mUid);
+        dest.writeString(mName);
+        dest.writeInt(mUnique ? 1 : 0);
+        dest.writeLong(mDuration);
+        dest.writeLong(mAvgPss);
+        dest.writeLong(mWeight);
+        dest.writeString(mBestTargetPackage);
+        dest.writeTypedList(mServices);
+    }
+
+    public static final Parcelable.Creator<ProcStatsEntry> CREATOR
+            = new Parcelable.Creator<ProcStatsEntry>() {
+        public ProcStatsEntry createFromParcel(Parcel in) {
+            return new ProcStatsEntry(in);
+        }
+
+        public ProcStatsEntry[] newArray(int size) {
+            return new ProcStatsEntry[size];
+        }
+    };
+
+    public static final class Service implements Parcelable {
         final String mPackage;
         final String mName;
         final long mDuration;
@@ -62,15 +218,51 @@
         public Service(ProcessStats.ServiceState service) {
             mPackage = service.mPackage;
             mName = service.mName;
-            mDuration = ProcessStats.dumpSingleServiceTime(null, null, service,
+            long startDuration = ProcessStats.dumpSingleServiceTime(null, null, service,
                     ProcessStats.ServiceState.SERVICE_STARTED,
-                    ProcessStats.STATE_NOTHING, 0, 0)
-                + ProcessStats.dumpSingleServiceTime(null, null, service,
+                    ProcessStats.STATE_NOTHING, 0, 0);
+            long bindDuration = ProcessStats.dumpSingleServiceTime(null, null, service,
                     ProcessStats.ServiceState.SERVICE_BOUND,
-                    ProcessStats.STATE_NOTHING, 0, 0)
-                + ProcessStats.dumpSingleServiceTime(null, null, service,
+                    ProcessStats.STATE_NOTHING, 0, 0);
+            long execDuration = ProcessStats.dumpSingleServiceTime(null, null, service,
                     ProcessStats.ServiceState.SERVICE_EXEC,
                     ProcessStats.STATE_NOTHING, 0, 0);
+            if (bindDuration > startDuration) {
+                startDuration = bindDuration;
+            }
+            if (execDuration > startDuration) {
+                startDuration = execDuration;
+            }
+            mDuration = startDuration;
         }
+
+        public Service(Parcel in) {
+            mPackage = in.readString();
+            mName = in.readString();
+            mDuration = in.readLong();
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(mPackage);
+            dest.writeString(mName);
+            dest.writeLong(mDuration);
+        }
+
+        public static final Parcelable.Creator<Service> CREATOR
+                = new Parcelable.Creator<Service>() {
+            public Service createFromParcel(Parcel in) {
+                return new Service(in);
+            }
+
+            public Service[] newArray(int size) {
+                return new Service[size];
+            }
+        };
     }
 }
diff --git a/src/com/android/settings/applications/ProcessStatsDetail.java b/src/com/android/settings/applications/ProcessStatsDetail.java
new file mode 100644
index 0000000..5ec3b07
--- /dev/null
+++ b/src/com/android/settings/applications/ProcessStatsDetail.java
@@ -0,0 +1,258 @@
+/*
+ * 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.app.Activity;
+import android.app.ActivityManager;
+import android.app.Fragment;
+import android.app.admin.DevicePolicyManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Process;
+import android.preference.PreferenceActivity;
+import android.text.format.Formatter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import com.android.settings.R;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+import static com.android.settings.Utils.prepareCustomPreferencesList;
+
+public class ProcessStatsDetail extends Fragment implements Button.OnClickListener {
+    private static final String TAG = "ProcessStatsDetail";
+
+    public static final int ACTION_FORCE_STOP = 1;
+
+    public static final String EXTRA_ENTRY = "entry";
+    public static final String EXTRA_MAX_WEIGHT = "max_weight";
+    public static final String EXTRA_TOTAL_TIME = "total_time";
+
+    private PackageManager mPm;
+    private DevicePolicyManager mDpm;
+
+    private ProcStatsEntry mEntry;
+    private long mMaxWeight;
+    private long mTotalTime;
+
+    private View mRootView;
+    private TextView mTitleView;
+    private ViewGroup mTwoButtonsPanel;
+    private Button mForceStopButton;
+    private Button mReportButton;
+    private ViewGroup mDetailsParent;
+    private ViewGroup mServicesParent;
+
+    public static String makePercentString(Resources res, long amount, long total) {
+        final double percent = (((double)amount) / total) * 100;
+        return res.getString(R.string.percentage, (int) Math.ceil(percent));
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        mPm = getActivity().getPackageManager();
+        mDpm = (DevicePolicyManager)getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
+        final Bundle args = getArguments();
+        mEntry = (ProcStatsEntry)args.getParcelable(EXTRA_ENTRY);
+        mEntry.retrieveUiData(mPm);
+        mMaxWeight = args.getLong(EXTRA_MAX_WEIGHT);
+        mTotalTime = args.getLong(EXTRA_TOTAL_TIME);
+    }
+
+    @Override
+    public View onCreateView(
+            LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        final View view = inflater.inflate(R.layout.process_stats_details, container, false);
+        prepareCustomPreferencesList(container, view, view, false);
+
+        mRootView = view;
+        createDetails();
+        return view;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        checkForceStop();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    private void createDetails() {
+        final double percentOfWeight = (((double)mEntry.mWeight) / mMaxWeight) * 100;
+
+        int appLevel = (int) Math.ceil(percentOfWeight);
+        String appLevelText = makePercentString(getResources(), mEntry.mDuration, mTotalTime);
+
+        // Set all values in the header.
+        final TextView summary = (TextView) mRootView.findViewById(android.R.id.summary);
+        summary.setText(mEntry.mName);
+        summary.setVisibility(View.VISIBLE);
+        mTitleView = (TextView) mRootView.findViewById(android.R.id.title);
+        mTitleView.setText(mEntry.mUiBaseLabel);
+        final TextView text1 = (TextView)mRootView.findViewById(android.R.id.text1);
+        text1.setText(appLevelText);
+        final ProgressBar progress = (ProgressBar) mRootView.findViewById(android.R.id.progress);
+        progress.setProgress(appLevel);
+        final ImageView icon = (ImageView) mRootView.findViewById(android.R.id.icon);
+        if (mEntry.mUiTargetApp != null) {
+            icon.setImageDrawable(mEntry.mUiTargetApp.loadIcon(mPm));
+        }
+
+        mTwoButtonsPanel = (ViewGroup)mRootView.findViewById(R.id.two_buttons_panel);
+        mForceStopButton = (Button)mRootView.findViewById(R.id.right_button);
+        mReportButton = (Button)mRootView.findViewById(R.id.left_button);
+        mForceStopButton.setEnabled(false);
+        mReportButton.setVisibility(View.INVISIBLE);
+
+        mDetailsParent = (ViewGroup)mRootView.findViewById(R.id.details);
+        mServicesParent = (ViewGroup)mRootView.findViewById(R.id.services);
+
+        fillDetailsSection();
+        fillServicesSection();
+
+        if (mEntry.mUid >= android.os.Process.FIRST_APPLICATION_UID) {
+            mForceStopButton.setText(R.string.force_stop);
+            mForceStopButton.setTag(ACTION_FORCE_STOP);
+            mForceStopButton.setOnClickListener(this);
+            mTwoButtonsPanel.setVisibility(View.VISIBLE);
+        } else {
+            mTwoButtonsPanel.setVisibility(View.GONE);
+        }
+    }
+
+    public void onClick(View v) {
+        doAction((Integer) v.getTag());
+    }
+
+    private void doAction(int action) {
+        PreferenceActivity pa = (PreferenceActivity)getActivity();
+        switch (action) {
+            case ACTION_FORCE_STOP:
+                killProcesses();
+                break;
+        }
+    }
+
+    private void addDetailsItem(ViewGroup parent, CharSequence label, CharSequence value) {
+        LayoutInflater inflater = getActivity().getLayoutInflater();
+        ViewGroup item = (ViewGroup) inflater.inflate(R.layout.power_usage_detail_item_text,
+                null);
+        parent.addView(item);
+        TextView labelView = (TextView) item.findViewById(R.id.label);
+        TextView valueView = (TextView) item.findViewById(R.id.value);
+        labelView.setText(label);
+        valueView.setText(value);
+    }
+
+    private void fillDetailsSection() {
+        addDetailsItem(mDetailsParent, getResources().getText(R.string.process_stats_ram_use),
+                Formatter.formatShortFileSize(getActivity(), mEntry.mAvgPss * 1024));
+        addDetailsItem(mDetailsParent, getResources().getText(R.string.process_stats_run_time),
+                makePercentString(getResources(), mEntry.mDuration, mTotalTime));
+    }
+
+    final static Comparator<ProcStatsEntry.Service> sServiceCompare
+            = new Comparator<ProcStatsEntry.Service>() {
+        @Override
+        public int compare(ProcStatsEntry.Service lhs, ProcStatsEntry.Service rhs) {
+            if (lhs.mDuration < rhs.mDuration) {
+                return 1;
+            } else if (lhs.mDuration > rhs.mDuration) {
+                return -1;
+            }
+            return 0;
+        }
+    };
+
+    private void fillServicesSection() {
+        LayoutInflater inflater = getActivity().getLayoutInflater();
+        if (mEntry.mServices.size() > 0) {
+            ArrayList<ProcStatsEntry.Service> services =
+                    (ArrayList<ProcStatsEntry.Service>)mEntry.mServices.clone();
+            Collections.sort(services, sServiceCompare);
+            for (int i=0; i<services.size(); i++) {
+                ProcStatsEntry.Service service = services.get(i);
+                String label = service.mName;
+                int tail = label.lastIndexOf('.');
+                if (tail >= 0 && tail < (label.length()-1)) {
+                    label = label.substring(tail+1);
+                }
+                long duration = service.mDuration;
+                final double percentOfTime = (((double)duration) / mTotalTime) * 100;
+                addDetailsItem(mServicesParent, label, getActivity().getResources().getString(
+                        R.string.percentage, (int) Math.ceil(percentOfTime)));
+            }
+        }
+    }
+
+    private void killProcesses() {
+        ActivityManager am = (ActivityManager)getActivity().getSystemService(
+                Context.ACTIVITY_SERVICE);
+        am.forceStopPackage(mEntry.mUiPackage);
+        checkForceStop();
+    }
+
+    private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mForceStopButton.setEnabled(getResultCode() != Activity.RESULT_CANCELED);
+        }
+    };
+
+    private void checkForceStop() {
+        if (mEntry.mUiPackage == null || mEntry.mUid < Process.FIRST_APPLICATION_UID) {
+            mForceStopButton.setEnabled(false);
+            return;
+        }
+        if (mDpm.packageHasActiveAdmins(mEntry.mUiPackage)) {
+            mForceStopButton.setEnabled(false);
+            return;
+        }
+        try {
+            ApplicationInfo info = mPm.getApplicationInfo(mEntry.mUiPackage, 0);
+            if ((info.flags&ApplicationInfo.FLAG_STOPPED) == 0) {
+                mForceStopButton.setEnabled(true);
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+        }
+        Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
+                Uri.fromParts("package", mEntry.mUiPackage, null));
+        intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mEntry.mUiPackage });
+        intent.putExtra(Intent.EXTRA_UID, mEntry.mUid);
+        intent.putExtra(Intent.EXTRA_USER_HANDLE, mEntry.mUid);
+        getActivity().sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null,
+                Activity.RESULT_CANCELED, null, null);
+    }
+}
diff --git a/src/com/android/settings/applications/ProcessStatsPreference.java b/src/com/android/settings/applications/ProcessStatsPreference.java
index 64ab0b6..2dea96a 100644
--- a/src/com/android/settings/applications/ProcessStatsPreference.java
+++ b/src/com/android/settings/applications/ProcessStatsPreference.java
@@ -27,15 +27,21 @@
 import com.android.settings.R;
 
 public class ProcessStatsPreference extends Preference {
+    private final ProcStatsEntry mEntry;
     private int mProgress;
     private CharSequence mProgressText;
 
-    public ProcessStatsPreference(Context context, Drawable icon) {
+    public ProcessStatsPreference(Context context, Drawable icon, ProcStatsEntry entry) {
         super(context);
+        mEntry = entry;
         setLayoutResource(R.layout.app_percentage_item);
         setIcon(icon != null ? icon : new ColorDrawable(0));
     }
 
+    public ProcStatsEntry getEntry() {
+        return mEntry;
+    }
+
     public void setPercent(double percentOfWeight, double percentOfTime) {
         mProgress = (int) Math.ceil(percentOfWeight);
         mProgressText = getContext().getResources().getString(
diff --git a/src/com/android/settings/applications/ProcessStatsUi.java b/src/com/android/settings/applications/ProcessStatsUi.java
index 8e98d23..49682ed 100644
--- a/src/com/android/settings/applications/ProcessStatsUi.java
+++ b/src/com/android/settings/applications/ProcessStatsUi.java
@@ -17,8 +17,6 @@
 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;
@@ -28,10 +26,10 @@
 import android.os.SystemClock;
 import android.os.UserManager;
 import android.preference.Preference;
+import android.preference.PreferenceActivity;
 import android.preference.PreferenceFragment;
 import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
-import android.text.format.DateFormat;
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.TimeUtils;
@@ -83,6 +81,7 @@
     private PreferenceGroup mAppListGroup;
     private Preference mMemStatusPref;
 
+    long mMaxWeight;
     long mTotalTime;
 
     @Override
@@ -127,11 +126,13 @@
             return false;
         }
 
-        /*
-        PreferenceActivity pa = (PreferenceActivity)getActivity();
-        pa.startPreferencePanel(PowerUsageDetail.class.getName(), args,
-                R.string.details_title, null, null, 0);
-        */
+        ProcessStatsPreference pgp = (ProcessStatsPreference) preference;
+        Bundle args = new Bundle();
+        args.putParcelable(ProcessStatsDetail.EXTRA_ENTRY, pgp.getEntry());
+        args.putLong(ProcessStatsDetail.EXTRA_MAX_WEIGHT, mMaxWeight);
+        args.putLong(ProcessStatsDetail.EXTRA_TOTAL_TIME, mTotalTime);
+        ((PreferenceActivity) getActivity()).startPreferencePanel(
+                ProcessStatsDetail.class.getName(), args, R.string.details_title, null, null, 0);
 
         return super.onPreferenceTreeClick(preferenceScreen, preference);
     }
@@ -263,108 +264,19 @@
                 maxWeight = proc.mWeight;
             }
         }
+        mMaxWeight = maxWeight;
 
         for (int i=0, N=(procs != null ? procs.size() : 0); i<N; i++) {
             ProcStatsEntry proc = procs.get(i);
             final double percentOfWeight = (((double)proc.mWeight) / maxWeight) * 100;
             final double percentOfTime = (((double)proc.mDuration) / mTotalTime) * 100;
             if (percentOfWeight < 1) continue;
-            ProcessStatsPreference pref = new ProcessStatsPreference(getActivity(), null);
-            ApplicationInfo targetApp = null;
-            String label = proc.mName;
-            String pkgName = null;
-            if (proc.mUnique) {
-                pkgName = proc.mPackage;
-                proc.addServices(mStats.getPackageStateLocked(proc.mPackage, proc.mUid));
-            } else {
-                // See if there is one significant package that was running here.
-                ArrayList<ProcStatsEntry> subProcs = new ArrayList<ProcStatsEntry>();
-                for (int ipkg=0, NPKG=mStats.mPackages.getMap().size(); ipkg<NPKG; ipkg++) {
-                    SparseArray<ProcessStats.PackageState> uids
-                            = mStats.mPackages.getMap().valueAt(ipkg);
-                    for (int iu=0, NU=uids.size(); iu<NU; iu++) {
-                        if (uids.keyAt(iu) != proc.mUid) {
-                            continue;
-                        }
-                        ProcessStats.PackageState pkgState = uids.valueAt(iu);
-                        boolean match = false;
-                        for (int iproc=0, NPROC=pkgState.mProcesses.size(); iproc<NPROC; iproc++) {
-                            ProcessStats.ProcessState subProc =
-                                    pkgState.mProcesses.valueAt(iproc);
-                            if (subProc.mName.equals(proc.mName)) {
-                                match = true;
-                                subProcs.add(new ProcStatsEntry(subProc, totals));
-                            }
-                        }
-                        if (match) {
-                            proc.addServices(mStats.getPackageStateLocked(proc.mPackage,
-                                    proc.mUid));
-                        }
-                    }
-                }
-                if ( subProcs.size() > 1) {
-                    Collections.sort(subProcs, sEntryCompare);
-                    if (subProcs.get(0).mWeight > (subProcs.get(1).mWeight*3)) {
-                        pkgName = subProcs.get(0).mPackage;
-                    }
-                }
-            }
-            if (pkgName != null) {
-                // Only one app associated with this process.
-                try {
-                    targetApp = pm.getApplicationInfo(pkgName,
-                            PackageManager.GET_DISABLED_COMPONENTS |
-                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS |
-                            PackageManager.GET_UNINSTALLED_PACKAGES);
-                    String name = targetApp.loadLabel(pm).toString();
-                    if (proc.mName.equals(pkgName)) {
-                        label = name;
-                    } else {
-                        if (proc.mName.startsWith(pkgName)) {
-                            int off = pkgName.length();
-                            if (proc.mName.length() > off) {
-                                off++;
-                            }
-                            label = name + " (" + proc.mName.substring(off) + ")";
-                        } else {
-                            label = name + " (" + proc.mName + ")";
-                        }
-                    }
-                } catch (PackageManager.NameNotFoundException e) {
-                }
-            }
-            if (targetApp == null) {
-                String[] packages = pm.getPackagesForUid(proc.mUid);
-                if (packages != null) {
-                    for (String curPkg : packages) {
-                        try {
-                            final PackageInfo pi = pm.getPackageInfo(curPkg,
-                                    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(curPkg,
-                                        pi.sharedUserLabel, pi.applicationInfo);
-                                if (nm != null) {
-                                    label = nm.toString() + " (" + proc.mName + ")";
-                                } else {
-                                    label = targetApp.loadLabel(pm).toString() + " ("
-                                            + proc.mName + ")";
-                                }
-                                break;
-                            }
-                        } catch (PackageManager.NameNotFoundException e) {
-                        }
-                    }
-                } else {
-                    // no current packages for this uid, typically because of uninstall
-                    Log.i(TAG, "No package for uid " + proc.mUid);
-                }
-            }
-            pref.setTitle(label);
-            if (targetApp != null) {
-                pref.setIcon(targetApp.loadIcon(pm));
+            ProcessStatsPreference pref = new ProcessStatsPreference(getActivity(), null, proc);
+            proc.evaluateTargetPackage(mStats, totals, sEntryCompare);
+            proc.retrieveUiData(pm);
+            pref.setTitle(proc.mUiLabel);
+            if (proc.mUiTargetApp != null) {
+                pref.setIcon(proc.mUiTargetApp.loadIcon(pm));
             }
             pref.setOrder(i);
             pref.setPercent(percentOfWeight, percentOfTime);