Add new button to report battery usage to the developer.

This displays a new button on the application battery usage details
screen for the user to send a bug report to the developer, if that is
possible to do.  Also adds a button to directly force stop the app
from the details screen, and uses the new facilities to determine
whether the button should be enabled.
This commit is contained in:
Dianne Hackborn
2010-02-21 20:07:27 -08:00
parent d814bb7309
commit cb497546ba
7 changed files with 257 additions and 36 deletions

View File

@@ -28,6 +28,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="2dip"
android:paddingBottom="4dip"
android:ellipsize="marquee"
android:textAppearance="?android:attr/textAppearanceMedium"/>

View File

@@ -87,6 +87,17 @@
</RelativeLayout>
</LinearLayout>
<!-- 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" />

View File

@@ -35,20 +35,20 @@
android:text="@string/no_running_services"
android:textAppearance="?android:attr/textAppearanceLarge" />
</FrameLayout>
<LinearLayout
<view class="com.android.settings.RunningServices$LinearColorBar"
android:id="@+id/color_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="?android:attr/colorForeground"
android:padding="4dp">
<TextView android:id="@+id/backgroundText"
<TextView android:id="@+id/foregroundText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textAppearance="?android:attr/textAppearanceSmallInverse"
android:color="?android:attr/textColorPrimaryInverse"
android:singleLine="true" />
<TextView android:id="@+id/foregroundText"
<TextView android:id="@+id/backgroundText"
android:layout_gravity="center_vertical|right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -56,5 +56,5 @@
android:textAppearance="?android:attr/textAppearanceSmallInverse"
android:color="?android:attr/textColorPrimaryInverse"
android:singleLine="true" />
</LinearLayout>
</view>
</LinearLayout>

View File

@@ -23,14 +23,13 @@ import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
import android.content.pm.IPackageMoveObserver;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.PackageInfo;
@@ -43,7 +42,6 @@ import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.text.format.Formatter;
import android.util.Config;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
@@ -234,25 +232,6 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
// Register listener
mUninstallButton.setOnClickListener(this);
}
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> rList = am.getRunningAppProcesses();
boolean running = false;
if (rList != null) {
for (RunningAppProcessInfo info : rList) {
if (info.pkgList != null) {
for (String rpkg : info.pkgList) {
if (rpkg.equals(mAppInfo.packageName)) {
running = true;
break;
}
}
}
}
}
mForceStopButton.setEnabled(running);
if (running) {
mForceStopButton.setOnClickListener(this);
}
}
/** Called when the activity is first created. */
@@ -292,6 +271,7 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
mForceStopButton = (Button) btnPanel.findViewById(R.id.left_button);
mForceStopButton.setText(R.string.force_stop);
mUninstallButton = (Button)btnPanel.findViewById(R.id.right_button);
mForceStopButton.setEnabled(false);
initControlButtons();
// Initialize clear data and move install location buttons
View data_buttons_panel = findViewById(R.id.data_buttons_panel);
@@ -384,6 +364,7 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
showDialogInner(DLG_APP_NOT_FOUND);
return;
}
checkForceStop();
refreshAppAttributes(pkgInfo);
}
@@ -604,6 +585,23 @@ public class InstalledAppDetails extends Activity implements View.OnClickListene
ActivityManager am = (ActivityManager)getSystemService(
Context.ACTIVITY_SERVICE);
am.forceStopPackage(pkgName);
checkForceStop();
}
private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mForceStopButton.setEnabled(getResultCode() != RESULT_CANCELED);
}
};
private void checkForceStop() {
Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
Uri.fromParts("package", mAppInfo.packageName, null));
intent.putExtra(Intent.EXTRA_PACKAGES, new String[] { mAppInfo.packageName });
intent.putExtra(Intent.EXTRA_UID, mAppInfo.uid);
sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null,
Activity.RESULT_CANCELED, null, null);
}
/*

View File

@@ -35,6 +35,9 @@ import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
@@ -53,6 +56,7 @@ import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
@@ -93,13 +97,16 @@ public class RunningServices extends ListActivity
int mProcessBgColor;
LinearColorBar mColorBar;
TextView mBackgroundProcessText;
TextView mForegroundProcessText;
int mLastNumBackgroundProcesses = -1;
int mLastNumForegroundProcesses = -1;
int mLastNumServiceProcesses = -1;
long mLastBackgroundProcessMemory = -1;
long mLastForegroundProcessMemory = -1;
long mLastServiceProcessMemory = -1;
long mLastAvailMemory = -1;
Dialog mCurDialog;
@@ -392,6 +399,8 @@ public class RunningServices extends ListActivity
long mBackgroundProcessMemory;
int mNumForegroundProcesses;
long mForegroundProcessMemory;
int mNumServiceProcesses;
long mServiceProcessMemory;
boolean update(Context context, ActivityManager am) {
final PackageManager pm = context.getPackageManager();
@@ -576,6 +585,7 @@ public class RunningServices extends ListActivity
mAllProcessItems.addAll(mProcessItems);
mNumBackgroundProcesses = 0;
mNumForegroundProcesses = 0;
mNumServiceProcesses = 0;
NRP = mRunningProcesses.size();
for (int i=0; i<NRP; i++) {
ProcessItem proc = mRunningProcesses.valueAt(i);
@@ -590,13 +600,19 @@ public class RunningServices extends ListActivity
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
mNumForegroundProcesses++;
mAllProcessItems.add(proc);
} else {
Log.i(TAG, "Unknown non-service process: "
+ proc.mProcessName + " #" + proc.mPid);
}
} else {
mNumServiceProcesses++;
}
}
try {
mBackgroundProcessMemory = 0;
mForegroundProcessMemory = 0;
mServiceProcessMemory = 0;
final int numProc = mAllProcessItems.size();
int[] pids = new int[numProc];
for (int i=0; i<numProc; i++) {
@@ -608,9 +624,8 @@ public class RunningServices extends ListActivity
ProcessItem proc = mAllProcessItems.get(i);
changed |= proc.updateSize(context, mem[i], mSequence);
if (proc.mCurSeq == mSequence) {
continue;
}
if (proc.mRunningProcessInfo.importance >=
mServiceProcessMemory += proc.mSize;
} else if (proc.mRunningProcessInfo.importance >=
ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
mBackgroundProcessMemory += proc.mSize;
} else if (proc.mRunningProcessInfo.importance <=
@@ -725,6 +740,67 @@ public class RunningServices extends ListActivity
}
}
public static class LinearColorBar extends LinearLayout {
private float mRedRatio;
private float mYellowRatio;
private float mGreenRatio;
final Rect mRect = new Rect();
final Paint mPaint = new Paint();
public LinearColorBar(Context context, AttributeSet attrs) {
super(context, attrs);
setWillNotDraw(false);
mPaint.setStyle(Paint.Style.FILL);
}
public void setRatios(float red, float yellow, float green) {
mRedRatio = red;
mYellowRatio = yellow;
mGreenRatio = green;
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
mRect.top = 0;
mRect.bottom = getHeight();
int left = 0;
int right = left + (int)(width*mRedRatio);
if (left < right) {
mRect.left = left;
mRect.right = right;
mPaint.setColor(0xffff8080);
canvas.drawRect(mRect, mPaint);
width -= (right-left);
left = right;
}
right = left + (int)(width*mYellowRatio);
if (left < right) {
mRect.left = left;
mRect.right = right;
mPaint.setColor(0xffffff00);
canvas.drawRect(mRect, mPaint);
width -= (right-left);
left = right;
}
right = left + width;
if (left < right) {
mRect.left = left;
mRect.right = right;
mPaint.setColor(0xff80ff80);
canvas.drawRect(mRect, mPaint);
}
}
}
final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
@@ -823,6 +899,7 @@ public class RunningServices extends ListActivity
setContentView(R.layout.running_services);
getListView().setDivider(null);
getListView().setAdapter(new ServiceListAdapter(mState));
mColorBar = (LinearColorBar)findViewById(R.id.color_bar);
mBackgroundProcessText = (TextView)findViewById(R.id.backgroundText);
mForegroundProcessText = (TextView)findViewById(R.id.foregroundText);
@@ -864,6 +941,14 @@ public class RunningServices extends ListActivity
mForegroundProcessText.setText(getResources().getString(
R.string.service_foreground_processes, mLastNumForegroundProcesses, sizeStr));
}
mLastNumServiceProcesses = mState.mNumServiceProcesses;
mLastServiceProcessMemory = mState.mServiceProcessMemory;
float totalMem = mLastBackgroundProcessMemory
+ mLastForegroundProcessMemory + mLastServiceProcessMemory;
mColorBar.setRatios(mLastForegroundProcessMemory/totalMem,
mLastServiceProcessMemory/totalMem,
mLastForegroundProcessMemory/totalMem);
}
@Override

View File

@@ -18,6 +18,9 @@ package com.android.settings.fuelgauge;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ApplicationErrorReport;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -26,9 +29,12 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Process;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -37,6 +43,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import com.android.settings.InstalledAppDetails;
import com.android.settings.ManageApplications;
import com.android.settings.R;
public class PowerUsageDetail extends Activity implements Button.OnClickListener {
@@ -68,6 +75,8 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener
public static final int ACTION_WIRELESS_SETTINGS = 4;
public static final int ACTION_APP_DETAILS = 5;
public static final int ACTION_SECURITY_SETTINGS = 6;
public static final int ACTION_FORCE_STOP = 7;
public static final int ACTION_REPORT = 8;
public static final int USAGE_SINCE_UNPLUGGED = 1;
public static final int USAGE_SINCE_RESET = 2;
@@ -78,6 +87,8 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener
public static final String EXTRA_UID = "uid";
public static final String EXTRA_USAGE_SINCE = "since";
public static final String EXTRA_USAGE_DURATION = "duration";
public static final String EXTRA_REPORT_DETAILS = "report_details";
public static final String EXTRA_REPORT_CHECKIN_DETAILS = "report_checkin_details";
public static final String EXTRA_DETAIL_TYPES = "types"; // Array of usage types (cpu, gps, etc)
public static final String EXTRA_DETAIL_VALUES = "values"; // Array of doubles
public static final String EXTRA_DRAIN_TYPE = "drainType"; // DrainType
@@ -92,6 +103,9 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener
private int mUid;
private double[] mValues;
private TextView mTitleView;
private ViewGroup mTwoButtonsPanel;
private Button mForceStopButton;
private Button mReportButton;
private ViewGroup mDetailsParent;
private ViewGroup mControlsParent;
private long mStartTime;
@@ -105,6 +119,9 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener
private static final String TAG = "PowerUsageDetail";
private String[] mPackages;
ApplicationInfo mApp;
ComponentName mInstaller;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -116,6 +133,7 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener
protected void onResume() {
super.onResume();
mStartTime = android.os.Process.getElapsedCpuTime();
checkForceStop();
}
@Override
@@ -163,6 +181,11 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener
((TextView)findViewById(R.id.battery_percentage))
.setText(String.format("%d%%", percentage));
mTwoButtonsPanel = (ViewGroup) findViewById(R.id.two_buttons_panel);
mForceStopButton = (Button) findViewById(R.id.left_button);
mReportButton = (Button) findViewById(R.id.right_button);
mForceStopButton.setEnabled(false);
ImageView gaugeImage = (ImageView) findViewById(R.id.gauge);
mGauge = new PercentageBar();
mGauge.percent = gaugeValue;
@@ -178,6 +201,34 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener
fillDetailsSection();
fillPackagesSection(mUid);
fillControlsSection(mUid);
if (mUid >= Process.FIRST_APPLICATION_UID) {
mForceStopButton.setText(R.string.force_stop);
mForceStopButton.setTag(ACTION_FORCE_STOP);
mForceStopButton.setOnClickListener(this);
mReportButton.setText(com.android.internal.R.string.report);
mReportButton.setTag(ACTION_REPORT);
mReportButton.setOnClickListener(this);
// 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) {
if (mPackages != null && mPackages.length > 0) {
try {
mApp = getPackageManager().getApplicationInfo(mPackages[0], 0);
mInstaller = ApplicationErrorReport.getErrorReportReceiver(
this, mPackages[0], mApp.flags);
} catch (NameNotFoundException e) {
}
}
mReportButton.setEnabled(mInstaller != null);
} else {
mTwoButtonsPanel.setVisibility(View.GONE);
}
} else {
mTwoButtonsPanel.setVisibility(View.GONE);
}
}
public void onClick(View v) {
@@ -201,12 +252,18 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener
case ACTION_APP_DETAILS:
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClass(this, InstalledAppDetails.class);
intent.putExtra("com.android.settings.ApplicationPkgName", mPackages[0]);
intent.putExtra(ManageApplications.APP_PKG_NAME, mPackages[0]);
startActivity(intent);
break;
case ACTION_SECURITY_SETTINGS:
startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
break;
case ACTION_FORCE_STOP:
killProcesses();
break;
case ACTION_REPORT:
reportBatteryUse();
break;
}
}
@@ -332,6 +389,60 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener
for (int i = 0; i < mPackages.length; i++) {
am.forceStopPackage(mPackages[i]);
}
checkForceStop();
}
private final BroadcastReceiver mCheckKillProcessesReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mForceStopButton.setEnabled(getResultCode() != RESULT_CANCELED);
}
};
private void checkForceStop() {
if (mPackages == null || mUid < Process.FIRST_APPLICATION_UID) {
mForceStopButton.setEnabled(false);
return;
}
Intent intent = new Intent(Intent.ACTION_QUERY_PACKAGE_RESTART,
Uri.fromParts("package", mPackages[0], null));
intent.putExtra(Intent.EXTRA_PACKAGES, mPackages);
intent.putExtra(Intent.EXTRA_UID, mUid);
sendOrderedBroadcast(intent, null, mCheckKillProcessesReceiver, null,
Activity.RESULT_CANCELED, null, null);
}
private void reportBatteryUse() {
if (mPackages == null) return;
final Intent intent = getIntent();
final int percentage = intent.getIntExtra(EXTRA_PERCENT, 1);
final long duration = intent.getLongExtra(EXTRA_USAGE_DURATION, 0);
ApplicationErrorReport report = new ApplicationErrorReport();
report.type = ApplicationErrorReport.TYPE_BATTERY;
report.packageName = mPackages[0];
report.installerPackageName = mInstaller.getPackageName();
report.processName = mPackages[0];
report.time = System.currentTimeMillis();
report.systemApp = (mApp.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
StringBuilder builder = new StringBuilder();
builder.append("Application used " + percentage + "% of battery over "
+ Utils.formatElapsedTime(this, duration / 1000));
builder.append('\n');
builder.append(intent.getStringExtra(EXTRA_REPORT_DETAILS));
builder.append('\n');
builder.append("----------------------------------------------");
builder.append('\n');
builder.append(intent.getStringExtra(EXTRA_REPORT_CHECKIN_DETAILS));
builder.append('\n');
report.batteryText = builder.toString();
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);
}
private void fillPackagesSection(int uid) {
@@ -344,7 +455,7 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener
LayoutInflater inflater = getLayoutInflater();
PackageManager pm = getPackageManager();
final Drawable defaultActivityIcon = pm.getDefaultActivityIcon();
//final Drawable defaultActivityIcon = pm.getDefaultActivityIcon();
mPackages = pm.getPackagesForUid(uid);
if (mPackages == null || mPackages.length < 2) {
removePackagesSection();
@@ -356,13 +467,13 @@ public class PowerUsageDetail extends Activity implements Button.OnClickListener
try {
ApplicationInfo ai = pm.getApplicationInfo(mPackages[i], 0);
CharSequence label = ai.loadLabel(pm);
Drawable icon = defaultActivityIcon;
//Drawable icon = defaultActivityIcon;
if (label != null) {
mPackages[i] = label.toString();
}
if (ai.icon != 0) {
icon = ai.loadIcon(pm);
}
//if (ai.icon != 0) {
// icon = ai.loadIcon(pm);
//}
ViewGroup item = (ViewGroup) inflater.inflate(R.layout.power_usage_package_item,
null);
packagesParent.addView(item);

View File

@@ -48,6 +48,9 @@ import com.android.internal.os.PowerProfile;
import com.android.settings.R;
import com.android.settings.fuelgauge.PowerUsageDetail.DrainType;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@@ -78,6 +81,7 @@ public class PowerUsageSummary extends PreferenceActivity implements Runnable {
private static final int MIN_POWER_THRESHOLD = 5;
private static final int MAX_ITEMS_TO_LIST = 10;
private long mStatsPeriod = 0;
private double mMaxPower = 1;
private double mTotalPower;
private PowerProfile mPowerProfile;
@@ -132,6 +136,7 @@ public class PowerUsageSummary extends PreferenceActivity implements Runnable {
Math.ceil(sipper.getSortValue() * 100 / mTotalPower));
intent.putExtra(PowerUsageDetail.EXTRA_GAUGE, (int)
Math.ceil(sipper.getSortValue() * 100 / mMaxPower));
intent.putExtra(PowerUsageDetail.EXTRA_USAGE_DURATION, mStatsPeriod);
intent.putExtra(PowerUsageDetail.EXTRA_ICON_PACKAGE, sipper.defaultPackageName);
intent.putExtra(PowerUsageDetail.EXTRA_ICON_ID, sipper.iconId);
intent.putExtra(PowerUsageDetail.EXTRA_NO_COVERAGE, sipper.noCoveragePercent);
@@ -165,6 +170,15 @@ public class PowerUsageSummary extends PreferenceActivity implements Runnable {
0
};
Writer result = new StringWriter();
PrintWriter printWriter = new PrintWriter(result);
mStats.dumpLocked(printWriter, "", mStatsType, uid.getUid());
intent.putExtra(PowerUsageDetail.EXTRA_REPORT_DETAILS, result.toString());
result = new StringWriter();
printWriter = new PrintWriter(result);
mStats.dumpCheckinLocked(printWriter, mStatsType, uid.getUid());
intent.putExtra(PowerUsageDetail.EXTRA_REPORT_CHECKIN_DETAILS, result.toString());
}
break;
case CELL:
@@ -303,6 +317,7 @@ public class PowerUsageSummary extends PreferenceActivity implements Runnable {
}
final double averageCostPerByte = getAverageDataCost();
long uSecTime = mStats.computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which);
mStatsPeriod = uSecTime;
updateStatsPeriod(uSecTime);
SparseArray<? extends Uid> uidStats = mStats.getUidStats();
final int NU = uidStats.size();