Files
packages_apps_Settings/src/com/android/settings/applications/ProcessStatsDetail.java
Jason Monk f4db340dae Consistently use weight to get average mem usage
Rather than just for the labels...

Also update bg mem to be converted to the same memory scale for
the bars.  Also undo some bad average calculations that only made
things worse.

Bug: 20694769
Change-Id: I676803bf76d336355441891e768ba3c228dbfbca
2015-05-07 15:44:37 -04:00

457 lines
18 KiB
Java

/*
* 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.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.AlertDialog;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.os.Process;
import android.preference.PreferenceCategory;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.android.internal.logging.MetricsLogger;
import com.android.settings.AppHeader;
import com.android.settings.CancellablePreference;
import com.android.settings.CancellablePreference.OnCancelListener;
import com.android.settings.R;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.applications.ProcStatsEntry.Service;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
public class ProcessStatsDetail extends SettingsPreferenceFragment
implements Button.OnClickListener {
private static final String TAG = "ProcessStatsDetail";
public static final int ACTION_FORCE_STOP = 1;
public static final String EXTRA_PACKAGE_ENTRY = "package_entry";
public static final String EXTRA_USE_USS = "use_uss";
public static final String EXTRA_WEIGHT_TO_RAM = "weight_to_ram";
public static final String EXTRA_TOTAL_TIME = "total_time";
public static final String EXTRA_MAX_MEMORY_USAGE = "max_memory_usage";
public static final String EXTRA_TOTAL_SCALE = "total_scale";
private static final String KEY_DETAILS_HEADER = "details_header";
private final ArrayMap<ComponentName, CancellablePreference> mServiceMap = new ArrayMap<>();
private PackageManager mPm;
private DevicePolicyManager mDpm;
private ProcStatsPackageEntry mApp;
private boolean mUseUss;
private double mWeightToRam;
private long mTotalTime;
private long mOnePercentTime;
private Button mForceStopButton;
private Button mReportButton;
private LinearColorBar mColorBar;
private double mMaxMemoryUsage;
private double mTotalScale;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mPm = getActivity().getPackageManager();
mDpm = (DevicePolicyManager)getActivity().getSystemService(Context.DEVICE_POLICY_SERVICE);
final Bundle args = getArguments();
mApp = args.getParcelable(EXTRA_PACKAGE_ENTRY);
mApp.retrieveUiData(getActivity(), mPm);
mUseUss = args.getBoolean(EXTRA_USE_USS);
mWeightToRam = args.getDouble(EXTRA_WEIGHT_TO_RAM);
mTotalTime = args.getLong(EXTRA_TOTAL_TIME);
mMaxMemoryUsage = args.getDouble(EXTRA_MAX_MEMORY_USAGE);
mTotalScale = args.getDouble(EXTRA_TOTAL_SCALE);
mOnePercentTime = mTotalTime/100;
mServiceMap.clear();
createDetails();
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
AppHeader.createAppHeader(this,
mApp.mUiTargetApp != null ? mApp.mUiTargetApp.loadIcon(mPm) : new ColorDrawable(0),
mApp.mUiLabel, null);
}
@Override
protected int getMetricsCategory() {
return MetricsLogger.APPLICATIONS_PROCESS_STATS_DETAIL;
}
@Override
public void onResume() {
super.onResume();
checkForceStop();
updateRunningServices();
}
private void updateRunningServices() {
ActivityManager activityManager = (ActivityManager)
getActivity().getSystemService(Context.ACTIVITY_SERVICE);
List<RunningServiceInfo> runningServices =
activityManager.getRunningServices(Integer.MAX_VALUE);
// Set all services as not running, then turn back on the ones we find.
int N = mServiceMap.size();
for (int i = 0; i < N; i++) {
mServiceMap.valueAt(i).setCancellable(false);
}
N = runningServices.size();
for (int i = 0; i < N; i++) {
RunningServiceInfo runningService = runningServices.get(i);
if (!runningService.started && runningService.clientLabel == 0) {
continue;
}
if ((runningService.flags & RunningServiceInfo.FLAG_PERSISTENT_PROCESS) != 0) {
continue;
}
final ComponentName service = runningService.service;
CancellablePreference pref = mServiceMap.get(service);
if (pref != null) {
pref.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(CancellablePreference preference) {
stopService(service.getPackageName(), service.getClassName());
}
});
pref.setCancellable(true);
}
}
}
private void createDetails() {
addPreferencesFromResource(R.xml.app_memory_settings);
fillProcessesSection();
LayoutPreference headerLayout = (LayoutPreference) findPreference(KEY_DETAILS_HEADER);
TextView avgUsed = (TextView) headerLayout.findViewById(R.id.memory_avg);
TextView maxUsed = (TextView) headerLayout.findViewById(R.id.memory_max);
avgUsed.setText(getString(R.string.memory_avg_desc,
Formatter.formatShortFileSize(getActivity(),
(long) (Math.max(mApp.mBgWeight, mApp.mRunWeight) * mWeightToRam))));
maxUsed.setText(getString(R.string.memory_max_desc,
Formatter.formatShortFileSize(getActivity(),
(long) (Math.max(mApp.mMaxBgMem, mApp.mMaxRunMem) * 1024 * mTotalScale))));
mForceStopButton = (Button) headerLayout.findViewById(R.id.right_button);
mReportButton = (Button) headerLayout.findViewById(R.id.left_button);
if (mApp.mEntries.get(0).mUid >= android.os.Process.FIRST_APPLICATION_UID) {
mForceStopButton.setEnabled(false);
mReportButton.setVisibility(View.INVISIBLE);
mForceStopButton.setText(R.string.force_stop);
mForceStopButton.setTag(ACTION_FORCE_STOP);
mForceStopButton.setOnClickListener(this);
} else {
mReportButton.setVisibility(View.GONE);
mForceStopButton.setVisibility(View.GONE);
}
// TODO: Find way to share this code with ProcessStatsPreference.
boolean statsForeground = mApp.mRunWeight > mApp.mBgWeight;
float avgRatio = (float) ((statsForeground ? mApp.mRunWeight : mApp.mBgWeight)
* mWeightToRam / mMaxMemoryUsage);
float maxRatio = (float) ((statsForeground ? mApp.mMaxRunMem : mApp.mMaxBgMem)
* mTotalScale * 1024 / mMaxMemoryUsage - avgRatio);
float remainingRatio = 1 - avgRatio - maxRatio;
mColorBar = (LinearColorBar) headerLayout.findViewById(R.id.color_bar);
Context context = getActivity();
mColorBar.setColors(context.getColor(R.color.memory_avg_use),
context.getColor(R.color.memory_max_use),
context.getColor(R.color.memory_remaining));
mColorBar.setRatios(avgRatio, maxRatio, remainingRatio);
}
public void onClick(View v) {
doAction((Integer) v.getTag());
}
private void doAction(int action) {
switch (action) {
case ACTION_FORCE_STOP:
killProcesses();
break;
}
}
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);
if (entry.mPackage.equals("os")) {
entry.mLabel = entry.mName;
} else {
entry.mLabel = getProcessName(mApp.mUiLabel, entry);
}
entries.add(entry);
}
Collections.sort(entries, sEntryCompare);
for (int ie = 0; ie < entries.size(); ie++) {
ProcStatsEntry entry = entries.get(ie);
PreferenceCategory processPref = new PreferenceCategory(getActivity());
processPref.setLayoutResource(R.layout.process_preference_category);
processPref.setTitle(entry.mLabel);
long duration = Math.max(entry.mRunDuration, entry.mBgDuration);
long memoryUse = Math.max((long) (entry.mRunWeight * mWeightToRam),
(long) (entry.mBgWeight * mWeightToRam));
String memoryString = Formatter.formatShortFileSize(getActivity(), memoryUse);
CharSequence frequency = ProcStatsPackageEntry.getFrequency(duration
/ (float)mTotalTime, getActivity());
processPref.setSummary(
getString(R.string.memory_use_running_format, memoryString, frequency));
getPreferenceScreen().addPreference(processPref);
fillServicesSection(entry, processPref);
}
}
private static String capitalize(String processName) {
char c = processName.charAt(0);
if (!Character.isLowerCase(c)) {
return processName;
}
return Character.toUpperCase(c) + processName.substring(1);
}
private static String getProcessName(String appLabel, ProcStatsEntry entry) {
String processName = entry.mName;
if (processName.contains(":")) {
return capitalize(processName.substring(processName.lastIndexOf(':') + 1));
}
if (processName.startsWith(entry.mPackage)) {
if (processName.length() == entry.mPackage.length()) {
return appLabel;
}
int start = entry.mPackage.length();
if (processName.charAt(start) == '.') {
start++;
}
return capitalize(processName.substring(start));
}
return processName;
}
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;
}
};
final static Comparator<PkgService> sServicePkgCompare = new Comparator<PkgService>() {
@Override
public int compare(PkgService lhs, PkgService rhs) {
if (lhs.mDuration < rhs.mDuration) {
return 1;
} 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(ProcStatsEntry entry, PreferenceCategory processPref) {
final HashMap<String, PkgService> pkgServices = new HashMap<>();
final ArrayList<PkgService> pkgList = new ArrayList<>();
for (int ip = 0; ip < entry.mServices.size(); ip++) {
String pkg = entry.mServices.keyAt(ip);
PkgService psvc = null;
ArrayList<ProcStatsEntry.Service> services = entry.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;
}
}
}
Collections.sort(pkgList, sServicePkgCompare);
for (int ip = 0; ip < pkgList.size(); ip++) {
ArrayList<ProcStatsEntry.Service> services = pkgList.get(ip).mServices;
Collections.sort(services, sServiceCompare);
for (int is=0; is<services.size(); is++) {
final ProcStatsEntry.Service service = services.get(is);
CharSequence label = getLabel(service);
CancellablePreference servicePref = new CancellablePreference(getActivity());
servicePref.setSelectable(false);
servicePref.setTitle(label);
servicePref.setSummary(ProcStatsPackageEntry.getFrequency(
service.mDuration / (float) mTotalTime, getActivity()));
processPref.addPreference(servicePref);
mServiceMap.put(new ComponentName(service.mPackage, service.mName), servicePref);
}
}
}
private CharSequence getLabel(Service service) {
// Try to get the service label, on the off chance that one exists.
try {
ServiceInfo serviceInfo = getPackageManager().getServiceInfo(
new ComponentName(service.mPackage, service.mName), 0);
if (serviceInfo.labelRes != 0) {
return serviceInfo.loadLabel(getPackageManager());
}
} catch (NameNotFoundException e) {
}
String label = service.mName;
int tail = label.lastIndexOf('.');
if (tail >= 0 && tail < (label.length()-1)) {
label = label.substring(tail+1);
}
return label;
}
private void stopService(String pkg, String name) {
try {
ApplicationInfo appInfo = getActivity().getPackageManager().getApplicationInfo(pkg, 0);
if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
showStopServiceDialog(pkg, name);
return;
}
} catch (NameNotFoundException e) {
Log.e(TAG, "Can't find app " + pkg, e);
return;
}
doStopService(pkg, name);
}
private void showStopServiceDialog(final String pkg, final String name) {
new AlertDialog.Builder(getActivity())
.setTitle(R.string.runningservicedetails_stop_dlg_title)
.setMessage(R.string.runningservicedetails_stop_dlg_text)
.setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
doStopService(pkg, name);
}
})
.setNegativeButton(R.string.dlg_cancel, null)
.show();
}
private void doStopService(String pkg, String name) {
getActivity().stopService(new Intent().setClassName(pkg, name));
updateRunningServices();
}
private void killProcesses() {
ActivityManager am = (ActivityManager)getActivity().getSystemService(
Context.ACTIVITY_SERVICE);
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 void checkForceStop() {
if (mApp.mEntries.get(0).mUid < Process.FIRST_APPLICATION_UID) {
mForceStopButton.setEnabled(false);
return;
}
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) {
}
}
}
if (isStarted) {
mForceStopButton.setEnabled(true);
}
}
}