- Show wake lock usage in the battery history. - Show WIFI usage in the battery history. - Show the amount an app has held a wake lock in the app details. - Also fix service run time computation in running services. Change-Id: I5aa959857f83808fbc709f65204f59bbfb73935b
463 lines
18 KiB
Java
463 lines
18 KiB
Java
package com.android.settings.applications;
|
|
|
|
import com.android.settings.R;
|
|
|
|
import android.app.Activity;
|
|
import android.app.ActivityManager;
|
|
import android.app.ApplicationErrorReport;
|
|
import android.app.PendingIntent;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.content.IntentSender;
|
|
import android.content.pm.ApplicationInfo;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.ProviderInfo;
|
|
import android.content.pm.ServiceInfo;
|
|
import android.content.pm.PackageManager.NameNotFoundException;
|
|
import android.content.res.Resources;
|
|
import android.os.Bundle;
|
|
import android.os.Debug;
|
|
import android.os.Handler;
|
|
import android.os.HandlerThread;
|
|
import android.os.Looper;
|
|
import android.os.Message;
|
|
import android.os.Process;
|
|
import android.os.SystemClock;
|
|
import android.provider.Settings;
|
|
import android.text.format.DateUtils;
|
|
import android.util.Log;
|
|
import android.view.LayoutInflater;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.widget.Button;
|
|
import android.widget.TextView;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
public class RunningServiceDetails extends Activity
|
|
implements RunningState.OnRefreshUiListener {
|
|
static final String TAG = "RunningServicesDetails";
|
|
|
|
static final String KEY_UID = "uid";
|
|
static final String KEY_PROCESS = "process";
|
|
|
|
ActivityManager mAm;
|
|
LayoutInflater mInflater;
|
|
|
|
RunningState mState;
|
|
|
|
int mUid;
|
|
String mProcessName;
|
|
|
|
RunningState.MergedItem mMergedItem;
|
|
|
|
ViewGroup mAllDetails;
|
|
ViewGroup mSnippet;
|
|
RunningProcessesView.ActiveItem mSnippetActiveItem;
|
|
RunningProcessesView.ViewHolder mSnippetViewHolder;
|
|
|
|
int mNumServices, mNumProcesses;
|
|
|
|
TextView mServicesHeader;
|
|
TextView mProcessesHeader;
|
|
final ArrayList<ActiveDetail> mActiveDetails = new ArrayList<ActiveDetail>();
|
|
|
|
class ActiveDetail implements View.OnClickListener {
|
|
View mRootView;
|
|
Button mStopButton;
|
|
Button mReportButton;
|
|
RunningState.ServiceItem mServiceItem;
|
|
RunningProcessesView.ActiveItem mActiveItem;
|
|
RunningProcessesView.ViewHolder mViewHolder;
|
|
PendingIntent mManageIntent;
|
|
ComponentName mInstaller;
|
|
|
|
public void onClick(View v) {
|
|
if (v == mReportButton) {
|
|
ApplicationErrorReport report = new ApplicationErrorReport();
|
|
report.type = ApplicationErrorReport.TYPE_RUNNING_SERVICE;
|
|
report.packageName = mServiceItem.mServiceInfo.packageName;
|
|
report.installerPackageName = mInstaller.getPackageName();
|
|
report.processName = mServiceItem.mRunningService.process;
|
|
report.time = System.currentTimeMillis();
|
|
report.systemApp = (mServiceItem.mServiceInfo.applicationInfo.flags
|
|
& ApplicationInfo.FLAG_SYSTEM) != 0;
|
|
ApplicationErrorReport.RunningServiceInfo info
|
|
= new ApplicationErrorReport.RunningServiceInfo();
|
|
if (mActiveItem.mFirstRunTime >= 0) {
|
|
info.durationMillis = SystemClock.elapsedRealtime()-mActiveItem.mFirstRunTime;
|
|
} else {
|
|
info.durationMillis = -1;
|
|
}
|
|
ComponentName comp = new ComponentName(mServiceItem.mServiceInfo.packageName,
|
|
mServiceItem.mServiceInfo.name);
|
|
File filename = getFileStreamPath("service_dump.txt");
|
|
FileOutputStream output = null;
|
|
try {
|
|
output = new FileOutputStream(filename);
|
|
Debug.dumpService("activity", output.getFD(),
|
|
new String[] { "-a", "service", comp.flattenToString() });
|
|
} catch (IOException e) {
|
|
Log.w(TAG, "Can't dump service: " + comp, e);
|
|
} finally {
|
|
if (output != null) try { output.close(); } catch (IOException e) {}
|
|
}
|
|
FileInputStream input = null;
|
|
try {
|
|
input = new FileInputStream(filename);
|
|
byte[] buffer = new byte[(int) filename.length()];
|
|
input.read(buffer);
|
|
info.serviceDetails = new String(buffer);
|
|
} catch (IOException e) {
|
|
Log.w(TAG, "Can't read service dump: " + comp, e);
|
|
} finally {
|
|
if (input != null) try { input.close(); } catch (IOException e) {}
|
|
}
|
|
filename.delete();
|
|
Log.i(TAG, "Details: " + info.serviceDetails);
|
|
report.runningServiceInfo = info;
|
|
Intent result = new Intent(Intent.ACTION_APP_ERROR);
|
|
result.setComponent(mInstaller);
|
|
result.putExtra(Intent.EXTRA_BUG_REPORT, report);
|
|
result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
startActivity(result);
|
|
return;
|
|
}
|
|
|
|
if (mManageIntent != null) {
|
|
try {
|
|
startIntentSender(mManageIntent.getIntentSender(), null,
|
|
Intent.FLAG_ACTIVITY_NEW_TASK
|
|
| Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET,
|
|
Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET, 0);
|
|
} catch (IntentSender.SendIntentException e) {
|
|
Log.w(TAG, e);
|
|
} catch (IllegalArgumentException e) {
|
|
Log.w(TAG, e);
|
|
} catch (ActivityNotFoundException e) {
|
|
Log.w(TAG, e);
|
|
}
|
|
} else if (mActiveItem.mItem instanceof RunningState.ServiceItem) {
|
|
RunningState.ServiceItem si = (RunningState.ServiceItem)mActiveItem.mItem;
|
|
stopService(new Intent().setComponent(si.mRunningService.service));
|
|
if (mMergedItem == null || mMergedItem.mServices.size() <= 1) {
|
|
// If there was only one service, we are finishing it,
|
|
// so no reason for the UI to stick around.
|
|
finish();
|
|
} else {
|
|
mState.updateNow();
|
|
}
|
|
} else {
|
|
// Heavy-weight process. We'll do a force-stop on it.
|
|
mAm.forceStopPackage(mActiveItem.mItem.mPackageInfo.packageName);
|
|
finish();
|
|
}
|
|
}
|
|
}
|
|
|
|
StringBuilder mBuilder = new StringBuilder(128);
|
|
|
|
boolean findMergedItem() {
|
|
RunningState.MergedItem item = null;
|
|
ArrayList<RunningState.MergedItem> newItems = mState.getCurrentMergedItems();
|
|
if (newItems != null) {
|
|
for (int i=0; i<newItems.size(); i++) {
|
|
RunningState.MergedItem mi = newItems.get(i);
|
|
if (mi.mProcess.mUid == mUid
|
|
&& mi.mProcess.mProcessName.equals(mProcessName)) {
|
|
item = mi;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (mMergedItem != item) {
|
|
mMergedItem = item;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void addServiceDetailsView(RunningState.ServiceItem si, RunningState.MergedItem mi) {
|
|
if (mNumServices == 0) {
|
|
mServicesHeader = (TextView)mInflater.inflate(R.layout.separator_label,
|
|
mAllDetails, false);
|
|
mServicesHeader.setText(R.string.runningservicedetails_services_title);
|
|
mAllDetails.addView(mServicesHeader);
|
|
}
|
|
mNumServices++;
|
|
|
|
RunningState.BaseItem bi = si != null ? si : mi;
|
|
|
|
ActiveDetail detail = new ActiveDetail();
|
|
View root = mInflater.inflate(R.layout.running_service_details_service,
|
|
mAllDetails, false);
|
|
mAllDetails.addView(root);
|
|
detail.mRootView = root;
|
|
detail.mServiceItem = si;
|
|
detail.mViewHolder = new RunningProcessesView.ViewHolder(root);
|
|
detail.mActiveItem = detail.mViewHolder.bind(mState, bi, mBuilder);
|
|
|
|
if (si != null && si.mRunningService.clientLabel != 0) {
|
|
detail.mManageIntent = mAm.getRunningServiceControlPanel(
|
|
si.mRunningService.service);
|
|
}
|
|
|
|
TextView description = (TextView)root.findViewById(R.id.comp_description);
|
|
if (si != null && si.mServiceInfo.descriptionRes != 0) {
|
|
description.setText(getPackageManager().getText(
|
|
si.mServiceInfo.packageName, si.mServiceInfo.descriptionRes,
|
|
si.mServiceInfo.applicationInfo));
|
|
} else {
|
|
if (detail.mManageIntent != null) {
|
|
try {
|
|
Resources clientr = getPackageManager().getResourcesForApplication(
|
|
si.mRunningService.clientPackage);
|
|
String label = clientr.getString(si.mRunningService.clientLabel);
|
|
description.setText(getString(R.string.service_manage_description,
|
|
label));
|
|
} catch (PackageManager.NameNotFoundException e) {
|
|
}
|
|
} else {
|
|
description.setText(getText(si != null
|
|
? R.string.service_stop_description
|
|
: R.string.heavy_weight_stop_description));
|
|
}
|
|
}
|
|
|
|
detail.mStopButton = (Button)root.findViewById(R.id.left_button);
|
|
detail.mStopButton.setOnClickListener(detail);
|
|
detail.mStopButton.setText(getText(detail.mManageIntent != null
|
|
? R.string.service_manage : R.string.service_stop));
|
|
|
|
detail.mReportButton = (Button)root.findViewById(R.id.right_button);
|
|
detail.mReportButton.setOnClickListener(detail);
|
|
detail.mReportButton.setText(com.android.internal.R.string.report);
|
|
// check if error reporting is enabled in secure settings
|
|
int enabled = Settings.Secure.getInt(getContentResolver(),
|
|
Settings.Secure.SEND_ACTION_APP_ERROR, 0);
|
|
if (enabled != 0) {
|
|
detail.mInstaller = ApplicationErrorReport.getErrorReportReceiver(
|
|
this, si.mServiceInfo.packageName, si.mServiceInfo.applicationInfo.flags);
|
|
detail.mReportButton.setEnabled(detail.mInstaller != null);
|
|
} else {
|
|
detail.mReportButton.setEnabled(false);
|
|
}
|
|
|
|
mActiveDetails.add(detail);
|
|
}
|
|
|
|
void addProcessDetailsView(RunningState.ProcessItem pi, boolean isMain) {
|
|
if (mNumProcesses == 0) {
|
|
mProcessesHeader = (TextView)mInflater.inflate(R.layout.separator_label,
|
|
mAllDetails, false);
|
|
mProcessesHeader.setText(R.string.runningservicedetails_processes_title);
|
|
mAllDetails.addView(mProcessesHeader);
|
|
}
|
|
mNumProcesses++;
|
|
|
|
ActiveDetail detail = new ActiveDetail();
|
|
View root = mInflater.inflate(R.layout.running_service_details_process,
|
|
mAllDetails, false);
|
|
mAllDetails.addView(root);
|
|
detail.mRootView = root;
|
|
detail.mViewHolder = new RunningProcessesView.ViewHolder(root);
|
|
detail.mActiveItem = detail.mViewHolder.bind(mState, pi, mBuilder);
|
|
|
|
TextView description = (TextView)root.findViewById(R.id.comp_description);
|
|
if (isMain) {
|
|
description.setText(R.string.main_running_process_description);
|
|
} else {
|
|
int textid = 0;
|
|
CharSequence label = null;
|
|
ActivityManager.RunningAppProcessInfo rpi = pi.mRunningProcessInfo;
|
|
final ComponentName comp = rpi.importanceReasonComponent;
|
|
//Log.i(TAG, "Secondary proc: code=" + rpi.importanceReasonCode
|
|
// + " pid=" + rpi.importanceReasonPid + " comp=" + comp);
|
|
switch (rpi.importanceReasonCode) {
|
|
case ActivityManager.RunningAppProcessInfo.REASON_PROVIDER_IN_USE:
|
|
textid = R.string.process_provider_in_use_description;
|
|
List<ProviderInfo> providers = null;
|
|
if (comp != null) {
|
|
providers = getPackageManager()
|
|
.queryContentProviders(comp.getPackageName(),
|
|
rpi.uid, 0);
|
|
}
|
|
if (providers != null) {
|
|
for (int j=0; j<providers.size(); j++) {
|
|
ProviderInfo prov = providers.get(j);
|
|
if (comp.getClassName().equals(prov.name)) {
|
|
label = RunningState.makeLabel(getPackageManager(),
|
|
prov.name, prov);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case ActivityManager.RunningAppProcessInfo.REASON_SERVICE_IN_USE:
|
|
textid = R.string.process_service_in_use_description;
|
|
if (rpi.importanceReasonComponent != null) {
|
|
try {
|
|
ServiceInfo serv = getPackageManager().getServiceInfo(
|
|
rpi.importanceReasonComponent, 0);
|
|
label = RunningState.makeLabel(getPackageManager(),
|
|
serv.name, serv);
|
|
} catch (NameNotFoundException e) {
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if (textid != 0 && label != null) {
|
|
description.setText(getString(textid, label));
|
|
}
|
|
}
|
|
|
|
mActiveDetails.add(detail);
|
|
}
|
|
|
|
void addDetailViews() {
|
|
for (int i=mActiveDetails.size()-1; i>=0; i--) {
|
|
mAllDetails.removeView(mActiveDetails.get(i).mRootView);
|
|
}
|
|
mActiveDetails.clear();
|
|
|
|
if (mServicesHeader != null) {
|
|
mAllDetails.removeView(mServicesHeader);
|
|
mServicesHeader = null;
|
|
}
|
|
|
|
if (mProcessesHeader != null) {
|
|
mAllDetails.removeView(mProcessesHeader);
|
|
mProcessesHeader = null;
|
|
}
|
|
|
|
mNumServices = mNumProcesses = 0;
|
|
|
|
if (mMergedItem != null) {
|
|
for (int i=0; i<mMergedItem.mServices.size(); i++) {
|
|
addServiceDetailsView(mMergedItem.mServices.get(i), mMergedItem);
|
|
}
|
|
|
|
if (mMergedItem.mServices.size() <= 0) {
|
|
// This item does not have any services, so it must be
|
|
// a heavy-weight process... we will put a fake service
|
|
// entry for it, to allow the user to "stop" it.
|
|
addServiceDetailsView(null, mMergedItem);
|
|
}
|
|
|
|
for (int i=-1; i<mMergedItem.mOtherProcesses.size(); i++) {
|
|
RunningState.ProcessItem pi = i < 0 ? mMergedItem.mProcess
|
|
: mMergedItem.mOtherProcesses.get(i);
|
|
if (pi.mPid <= 0) {
|
|
continue;
|
|
}
|
|
|
|
addProcessDetailsView(pi, i < 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void refreshUi(boolean dataChanged) {
|
|
if (findMergedItem()) {
|
|
dataChanged = true;
|
|
}
|
|
if (dataChanged) {
|
|
if (mMergedItem != null) {
|
|
mSnippetActiveItem = mSnippetViewHolder.bind(mState,
|
|
mMergedItem, mBuilder);
|
|
} else if (mSnippetActiveItem != null) {
|
|
// Clear whatever is currently being shown.
|
|
mSnippetActiveItem.mHolder.size.setText("");
|
|
mSnippetActiveItem.mHolder.uptime.setText("");
|
|
mSnippetActiveItem.mHolder.description.setText(R.string.no_services);
|
|
} else {
|
|
// No merged item, never had one. Nothing to do.
|
|
finish();
|
|
return;
|
|
}
|
|
addDetailViews();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
mUid = getIntent().getIntExtra(KEY_UID, 0);
|
|
mProcessName = getIntent().getStringExtra(KEY_PROCESS);
|
|
|
|
mAm = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
|
|
mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
|
|
|
mState = RunningState.getInstance(this);
|
|
|
|
setContentView(R.layout.running_service_details);
|
|
|
|
mAllDetails = (ViewGroup)findViewById(R.id.all_details);
|
|
mSnippet = (ViewGroup)findViewById(R.id.snippet);
|
|
mSnippet.setBackgroundResource(com.android.internal.R.drawable.title_bar_medium);
|
|
mSnippet.setPadding(0, mSnippet.getPaddingTop(), 0, mSnippet.getPaddingBottom());
|
|
mSnippetViewHolder = new RunningProcessesView.ViewHolder(mSnippet);
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
super.onPause();
|
|
mState.pause();
|
|
}
|
|
|
|
@Override
|
|
protected void onResume() {
|
|
super.onResume();
|
|
mState.resume(this);
|
|
|
|
// We want to go away if the service being shown no longer exists,
|
|
// so we need to ensure we have done the initial data retrieval before
|
|
// showing our ui.
|
|
mState.waitForData();
|
|
|
|
// And since we know we have the data, let's show the UI right away
|
|
// to avoid flicker.
|
|
refreshUi(true);
|
|
}
|
|
|
|
@Override
|
|
protected void onSaveInstanceState(Bundle outState) {
|
|
super.onSaveInstanceState(outState);
|
|
}
|
|
|
|
void updateTimes() {
|
|
if (mSnippetActiveItem != null) {
|
|
mSnippetActiveItem.updateTime(RunningServiceDetails.this, mBuilder);
|
|
}
|
|
for (int i=0; i<mActiveDetails.size(); i++) {
|
|
mActiveDetails.get(i).mActiveItem.updateTime(
|
|
RunningServiceDetails.this, mBuilder);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onRefreshUi(int what) {
|
|
switch (what) {
|
|
case REFRESH_TIME:
|
|
updateTimes();
|
|
break;
|
|
case REFRESH_DATA:
|
|
refreshUi(false);
|
|
updateTimes();
|
|
break;
|
|
case REFRESH_STRUCTURE:
|
|
refreshUi(true);
|
|
updateTimes();
|
|
break;
|
|
}
|
|
}
|
|
}
|