Make proc stats UI app-centric instead of process-centric.

The UI now shows a much simpler overview of memory use for
the current measured duration, computing the weighted RAM
user per application.  (This is done basically by rolling up
each individual process into an app that can contain multiple
processes, and using the recently introduced weighted RAM for
all data processing.)

The details screen is updated to reflect this new design,
showing an overview of a particular application, which
separate entries for each process running for that app.

Change-Id: I47d79c30086d733eb37440a6c21b18a92b767d01
This commit is contained in:
Dianne Hackborn
2015-01-30 18:19:38 -08:00
parent 7221a6bcaa
commit b94079a704
8 changed files with 647 additions and 397 deletions

View File

@@ -16,20 +16,14 @@
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.os.UserHandle;
import android.text.format.Formatter;
import android.view.LayoutInflater;
import android.view.View;
@@ -44,6 +38,7 @@ import com.android.settings.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import static com.android.settings.Utils.prepareCustomPreferencesList;
@@ -52,25 +47,28 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen
public static final int ACTION_FORCE_STOP = 1;
public static final String EXTRA_ENTRY = "entry";
public static final String EXTRA_PACKAGE_ENTRY = "package_entry";
public static final String EXTRA_USE_USS = "use_uss";
public static final String EXTRA_MAX_WEIGHT = "max_weight";
public static final String EXTRA_WEIGHT_TO_RAM = "weight_to_ram";
public static final String EXTRA_TOTAL_TIME = "total_time";
private PackageManager mPm;
private DevicePolicyManager mDpm;
private ProcStatsEntry mEntry;
private ProcStatsPackageEntry mApp;
private boolean mUseUss;
private long mMaxWeight;
private double mMaxWeight;
private double mWeightToRam;
private long mTotalTime;
private long mOnePercentTime;
private View mRootView;
private TextView mTitleView;
private ViewGroup mTwoButtonsPanel;
private Button mForceStopButton;
private Button mReportButton;
private ViewGroup mDetailsParent;
private ViewGroup mProcessesParent;
private ViewGroup mServicesParent;
@Override
@@ -79,11 +77,13 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen
mPm = getActivity().getPackageManager();
mDpm = (DevicePolicyManager)getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
final Bundle args = getArguments();
mEntry = (ProcStatsEntry)args.getParcelable(EXTRA_ENTRY);
mEntry.retrieveUiData(mPm);
mApp = args.getParcelable(EXTRA_PACKAGE_ENTRY);
mApp.retrieveUiData(getActivity(), mPm);
mUseUss = args.getBoolean(EXTRA_USE_USS);
mMaxWeight = args.getLong(EXTRA_MAX_WEIGHT);
mMaxWeight = args.getDouble(EXTRA_MAX_WEIGHT);
mWeightToRam = args.getDouble(EXTRA_WEIGHT_TO_RAM);
mTotalTime = args.getLong(EXTRA_TOTAL_TIME);
mOnePercentTime = mTotalTime/100;
}
@Override
@@ -109,24 +109,22 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen
}
private void createDetails() {
final double percentOfWeight = (((double)mEntry.mWeight) / mMaxWeight) * 100;
final double percentOfWeight = (mApp.mBgWeight / mMaxWeight) * 100;
int appLevel = (int) Math.ceil(percentOfWeight);
String appLevelText = Utils.formatPercentage(mEntry.mDuration, mTotalTime);
String appLevelText = Formatter.formatShortFileSize(getActivity(),
(long)(mApp.mRunWeight * mWeightToRam));
// 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);
mTitleView.setText(mApp.mUiLabel);
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));
if (mApp.mUiTargetApp != null) {
icon.setImageDrawable(mApp.mUiTargetApp.loadIcon(mPm));
}
mTwoButtonsPanel = (ViewGroup)mRootView.findViewById(R.id.two_buttons_panel);
@@ -135,13 +133,17 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen
mForceStopButton.setEnabled(false);
mReportButton.setVisibility(View.INVISIBLE);
mDetailsParent = (ViewGroup)mRootView.findViewById(R.id.details);
mProcessesParent = (ViewGroup)mRootView.findViewById(R.id.processes);
mServicesParent = (ViewGroup)mRootView.findViewById(R.id.services);
fillDetailsSection();
fillProcessesSection();
fillServicesSection();
if (mServicesParent.getChildCount() <= 0) {
mServicesParent.setVisibility(View.GONE);
mRootView.findViewById(R.id.services_label).setVisibility(View.GONE);
}
if (mEntry.mUid >= android.os.Process.FIRST_APPLICATION_UID) {
if (mApp.mEntries.get(0).mUid >= android.os.Process.FIRST_APPLICATION_UID) {
mForceStopButton.setText(R.string.force_stop);
mForceStopButton.setTag(ACTION_FORCE_STOP);
mForceStopButton.setOnClickListener(this);
@@ -191,15 +193,43 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen
valueView.setText(value);
}
private void fillDetailsSection() {
addDetailsItem(mDetailsParent, getResources().getText(R.string.process_stats_avg_ram_use),
Formatter.formatShortFileSize(getActivity(),
(mUseUss ? mEntry.mAvgUss : mEntry.mAvgPss) * 1024));
addDetailsItem(mDetailsParent, getResources().getText(R.string.process_stats_max_ram_use),
Formatter.formatShortFileSize(getActivity(),
(mUseUss ? mEntry.mMaxUss : mEntry.mMaxPss) * 1024));
addDetailsItem(mDetailsParent, getResources().getText(R.string.process_stats_run_time),
Utils.formatPercentage(mEntry.mDuration, mTotalTime));
final static Comparator<ProcStatsEntry> sEntryCompare = new Comparator<ProcStatsEntry>() {
@Override
public int compare(ProcStatsEntry lhs, ProcStatsEntry rhs) {
if (lhs.mRunWeight < rhs.mRunWeight) {
return 1;
} else if (lhs.mRunWeight > rhs.mRunWeight) {
return -1;
}
return 0;
}
};
private void fillProcessesSection() {
final ArrayList<ProcStatsEntry> entries = new ArrayList<>();
for (int ie=0; ie<mApp.mEntries.size(); ie++) {
ProcStatsEntry entry = mApp.mEntries.get(ie);
entries.add(entry);
}
Collections.sort(entries, sEntryCompare);
for (int ie=0; ie<entries.size(); ie++) {
ProcStatsEntry entry = entries.get(ie);
LayoutInflater inflater = getActivity().getLayoutInflater();
ViewGroup item = (ViewGroup) inflater.inflate(R.layout.process_stats_proc_details,
null);
mProcessesParent.addView(item);
((TextView)item.findViewById(R.id.processes_name)).setText(entry.mName);
addDetailsItem(item, getResources().getText(R.string.process_stats_ram_use),
Formatter.formatShortFileSize(getActivity(),
(long)(entry.mRunWeight * mWeightToRam)));
if (entry.mBgWeight > 0) {
addDetailsItem(item, getResources().getText(R.string.process_stats_bg_ram_use),
Formatter.formatShortFileSize(getActivity(),
(long)(entry.mBgWeight * mWeightToRam)));
}
addDetailsItem(item, getResources().getText(R.string.process_stats_run_time),
Utils.formatPercentage(entry.mRunDuration, mTotalTime));
}
}
final static Comparator<ProcStatsEntry.Service> sServiceCompare
@@ -215,95 +245,106 @@ public class ProcessStatsDetail extends Fragment implements Button.OnClickListen
}
};
final static Comparator<ArrayList<ProcStatsEntry.Service>> sServicePkgCompare
= new Comparator<ArrayList<ProcStatsEntry.Service>>() {
final static Comparator<PkgService> sServicePkgCompare = new Comparator<PkgService>() {
@Override
public int compare(ArrayList<ProcStatsEntry.Service> lhs,
ArrayList<ProcStatsEntry.Service> rhs) {
long topLhs = lhs.size() > 0 ? lhs.get(0).mDuration : 0;
long topRhs = rhs.size() > 0 ? rhs.get(0).mDuration : 0;
if (topLhs < topRhs) {
public int compare(PkgService lhs, PkgService rhs) {
if (lhs.mDuration < rhs.mDuration) {
return 1;
} else if (topLhs > topRhs) {
} else if (lhs.mDuration > rhs.mDuration) {
return -1;
}
return 0;
}
};
static class PkgService {
final ArrayList<ProcStatsEntry.Service> mServices = new ArrayList<>();
long mDuration;
}
private void fillServicesSection() {
if (mEntry.mServices.size() > 0) {
boolean addPackageSections = false;
// Sort it all.
ArrayList<ArrayList<ProcStatsEntry.Service>> servicePkgs
= new ArrayList<ArrayList<ProcStatsEntry.Service>>();
for (int ip=0; ip<mEntry.mServices.size(); ip++) {
ArrayList<ProcStatsEntry.Service> services =
(ArrayList<ProcStatsEntry.Service>)mEntry.mServices.valueAt(ip).clone();
Collections.sort(services, sServiceCompare);
servicePkgs.add(services);
}
if (mEntry.mServices.size() > 1
|| !mEntry.mServices.valueAt(0).get(0).mPackage.equals(mEntry.mPackage)) {
addPackageSections = true;
// Sort these so that the one(s) with the longest run durations are on top.
Collections.sort(servicePkgs, sServicePkgCompare);
}
for (int ip=0; ip<servicePkgs.size(); ip++) {
ArrayList<ProcStatsEntry.Service> services = servicePkgs.get(ip);
if (addPackageSections) {
addPackageHeaderItem(mServicesParent, services.get(0).mPackage);
}
for (int is=0; is<services.size(); is++) {
ProcStatsEntry.Service service = services.get(is);
String label = service.mName;
int tail = label.lastIndexOf('.');
if (tail >= 0 && tail < (label.length()-1)) {
label = label.substring(tail+1);
final HashMap<String, PkgService> pkgServices = new HashMap<>();
final ArrayList<PkgService> pkgList = new ArrayList<>();
for (int ie=0; ie< mApp.mEntries.size(); ie++) {
ProcStatsEntry ent = mApp.mEntries.get(ie);
for (int ip=0; ip<ent.mServices.size(); ip++) {
String pkg = ent.mServices.keyAt(ip);
PkgService psvc = null;
ArrayList<ProcStatsEntry.Service> services = ent.mServices.valueAt(ip);
for (int is=services.size()-1; is>=0; is--) {
ProcStatsEntry.Service pent = services.get(is);
if (pent.mDuration >= mOnePercentTime) {
if (psvc == null) {
psvc = pkgServices.get(pkg);
if (psvc == null) {
psvc = new PkgService();
pkgServices.put(pkg, psvc);
pkgList.add(psvc);
}
}
psvc.mServices.add(pent);
psvc.mDuration += pent.mDuration;
}
String percentage = Utils.formatPercentage(service.mDuration, mTotalTime);
addDetailsItem(mServicesParent, label, percentage);
}
}
}
Collections.sort(pkgList, sServicePkgCompare);
for (int ip=0; ip<pkgList.size(); ip++) {
ArrayList<ProcStatsEntry.Service> services = pkgList.get(ip).mServices;
Collections.sort(services, sServiceCompare);
if (pkgList.size() > 1) {
addPackageHeaderItem(mServicesParent, services.get(0).mPackage);
}
for (int is=0; is<services.size(); is++) {
ProcStatsEntry.Service service = services.get(is);
String label = service.mName;
int tail = label.lastIndexOf('.');
if (tail >= 0 && tail < (label.length()-1)) {
label = label.substring(tail+1);
}
String percentage = Utils.formatPercentage(service.mDuration, mTotalTime);
addDetailsItem(mServicesParent, label, percentage);
}
}
}
private void killProcesses() {
ActivityManager am = (ActivityManager)getActivity().getSystemService(
Context.ACTIVITY_SERVICE);
am.forceStopPackage(mEntry.mUiPackage);
for (int i=0; i< mApp.mEntries.size(); i++) {
ProcStatsEntry ent = mApp.mEntries.get(i);
for (int j=0; j<ent.mPackages.size(); j++) {
am.forceStopPackage(ent.mPackages.get(j));
}
}
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) {
if (mApp.mEntries.get(0).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);
boolean isStarted = false;
for (int i=0; i< mApp.mEntries.size(); i++) {
ProcStatsEntry ent = mApp.mEntries.get(i);
for (int j=0; j<ent.mPackages.size(); j++) {
String pkg = ent.mPackages.get(j);
if (mDpm.packageHasActiveAdmins(pkg)) {
mForceStopButton.setEnabled(false);
return;
}
try {
ApplicationInfo info = mPm.getApplicationInfo(pkg, 0);
if ((info.flags&ApplicationInfo.FLAG_STOPPED) == 0) {
isStarted = true;
}
} catch (PackageManager.NameNotFoundException e) {
}
}
} 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, UserHandle.getUserId(mEntry.mUid));
getActivity().sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null,
Activity.RESULT_CANCELED, null, null);
if (isStarted) {
mForceStopButton.setEnabled(true);
}
}
}