Files
app_Settings/src/com/android/settings/applications/ProcessStatsDetail.java
Dianne Hackborn 5635594c38 Fix issue #10948509: Crash in procstats when there is no data
Also make us much better about determining which app to blame for
a particular running process, organize the display of services by
their owning app, apply some new heuristics for deciding what goes
in the process list so that we can still display interesting processes
when we haven't collected pss data for them yet, etc.

Change-Id: I7e87225d3dabc66cf3ff020b5db48401b14aaf47
2013-09-29 17:07:49 -07:00

292 lines
12 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.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_USE_USS = "use_uss";
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 boolean mUseUss;
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);
mUseUss = args.getBoolean(EXTRA_USE_USS);
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 addPackageHeaderItem(ViewGroup parent, String packageName) {
LayoutInflater inflater = getActivity().getLayoutInflater();
ViewGroup item = (ViewGroup) inflater.inflate(R.layout.running_processes_item,
null);
parent.addView(item);
final ImageView icon = (ImageView) item.findViewById(R.id.icon);
TextView nameView = (TextView) item.findViewById(R.id.name);
TextView descriptionView = (TextView) item.findViewById(R.id.description);
try {
ApplicationInfo ai = mPm.getApplicationInfo(packageName, 0);
icon.setImageDrawable(ai.loadIcon(mPm));
nameView.setText(ai.loadLabel(mPm));
} catch (PackageManager.NameNotFoundException e) {
}
descriptionView.setText(packageName);
}
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_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),
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() {
if (mEntry.mServices.size() > 0) {
boolean addPackageSections = false;
if (mEntry.mServices.size() > 1
|| !mEntry.mServices.valueAt(0).get(0).mPackage.equals(mEntry.mPackage)) {
addPackageSections = true;
}
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);
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);
}
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);
}
}