Allow user to see and stop heavy-weight processes.

Change-Id: If5caed3972ab03a54fbf8c459cdfc136e4bdc020
This commit is contained in:
Dianne Hackborn
2010-06-24 11:29:50 -07:00
parent 12fd447d6f
commit c01b0c83fc
5 changed files with 242 additions and 148 deletions

View File

@@ -1788,6 +1788,9 @@ found in the list of installed applications.</string>
<!-- Running service details, default description for services that are started. --> <!-- Running service details, default description for services that are started. -->
<string name="service_stop_description">This service was started by its <string name="service_stop_description">This service was started by its
application. Stopping it may cause the application to fail.</string> application. Stopping it may cause the application to fail.</string>
<!-- Running service details, description for running heavy-weight process. -->
<string name="heavy_weight_stop_description">This application can not safely
be stopped. Doing so may lose some of your current work.</string>
<!-- Running service details, default description for services that are managed. --> <!-- Running service details, default description for services that are managed. -->
<string name="service_manage_description"><xliff:g id="client_name">%1$s</xliff:g>: <string name="service_manage_description"><xliff:g id="client_name">%1$s</xliff:g>:
currently in use. Touch Settings to control it.</string> currently in use. Touch Settings to control it.</string>

View File

@@ -130,8 +130,16 @@ public class RunningProcessesView extends FrameLayout
uptimeView.setText(DateUtils.formatElapsedTime(builder, uptimeView.setText(DateUtils.formatElapsedTime(builder,
(SystemClock.uptimeMillis()-mFirstRunTime)/1000)); (SystemClock.uptimeMillis()-mFirstRunTime)/1000));
} else { } else {
uptimeView.setText(context.getResources().getText( boolean isService = false;
R.string.service_restarting)); if (mItem instanceof RunningState.MergedItem) {
isService = ((RunningState.MergedItem)mItem).mServices.size() > 0;
}
if (isService) {
uptimeView.setText(context.getResources().getText(
R.string.service_restarting));
} else {
uptimeView.setText("");
}
} }
} }
} }

View File

@@ -55,6 +55,8 @@ public class RunningServiceDetails extends Activity {
RunningProcessesView.ActiveItem mSnippetActiveItem; RunningProcessesView.ActiveItem mSnippetActiveItem;
RunningProcessesView.ViewHolder mSnippetViewHolder; RunningProcessesView.ViewHolder mSnippetViewHolder;
int mNumServices, mNumProcesses;
TextView mServicesHeader; TextView mServicesHeader;
TextView mProcessesHeader; TextView mProcessesHeader;
final ArrayList<ActiveDetail> mActiveDetails = new ArrayList<ActiveDetail>(); final ArrayList<ActiveDetail> mActiveDetails = new ArrayList<ActiveDetail>();
@@ -79,7 +81,7 @@ public class RunningServiceDetails extends Activity {
} catch (ActivityNotFoundException e) { } catch (ActivityNotFoundException e) {
Log.w(TAG, e); Log.w(TAG, e);
} }
} else { } else if (mActiveItem.mItem instanceof RunningState.ServiceItem) {
RunningState.ServiceItem si = (RunningState.ServiceItem)mActiveItem.mItem; RunningState.ServiceItem si = (RunningState.ServiceItem)mActiveItem.mItem;
stopService(new Intent().setComponent(si.mRunningService.service)); stopService(new Intent().setComponent(si.mRunningService.service));
if (mMergedItem == null || mMergedItem.mServices.size() <= 1) { if (mMergedItem == null || mMergedItem.mServices.size() <= 1) {
@@ -91,6 +93,10 @@ public class RunningServiceDetails extends Activity {
mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS); mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS);
} }
} }
} else {
// Heavy-weight process. We'll do a force-stop on it.
mAm.forceStopPackage(mActiveItem.mItem.mPackageInfo.packageName);
finish();
} }
} }
} }
@@ -163,6 +169,129 @@ public class RunningServiceDetails extends Activity {
return false; 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.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));
}
}
View button = root.findViewById(R.id.right_button);
button.setOnClickListener(detail);
((TextView)button).setText(getText(detail.mManageIntent != null
? R.string.service_manage : R.string.service_stop));
root.findViewById(R.id.left_button).setVisibility(View.INVISIBLE);
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() { void addDetailViews() {
for (int i=mActiveDetails.size()-1; i>=0; i--) { for (int i=mActiveDetails.size()-1; i>=0; i--) {
mAllDetails.removeView(mActiveDetails.get(i).mRootView); mAllDetails.removeView(mActiveDetails.get(i).mRootView);
@@ -179,58 +308,20 @@ public class RunningServiceDetails extends Activity {
mProcessesHeader = null; mProcessesHeader = null;
} }
mNumServices = mNumProcesses = 0;
if (mMergedItem != null) { if (mMergedItem != null) {
for (int i=0; i<mMergedItem.mServices.size(); i++) { for (int i=0; i<mMergedItem.mServices.size(); i++) {
if (i == 0) { addServiceDetailsView(mMergedItem.mServices.get(i), mMergedItem);
mServicesHeader = (TextView)mInflater.inflate(R.layout.separator_label, }
mAllDetails, false);
mServicesHeader.setText(R.string.runningservicedetails_services_title); if (mMergedItem.mServices.size() <= 0) {
mAllDetails.addView(mServicesHeader); // This item does not have any services, so it must be
} // a heavy-weight process... we will put a fake service
RunningState.ServiceItem si = mMergedItem.mServices.get(i); // entry for it, to allow the user to "stop" it.
ActiveDetail detail = new ActiveDetail(); addServiceDetailsView(null, mMergedItem);
View root = mInflater.inflate(R.layout.running_service_details_service,
mAllDetails, false);
mAllDetails.addView(root);
detail.mRootView = root;
detail.mViewHolder = new RunningProcessesView.ViewHolder(root);
detail.mActiveItem = detail.mViewHolder.bind(mState, si, mBuilder);
if (si.mRunningService.clientLabel != 0) {
detail.mManageIntent = mAm.getRunningServiceControlPanel(
si.mRunningService.service);
}
TextView description = (TextView)root.findViewById(R.id.comp_description);
if (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(R.string.service_stop_description));
}
}
View button = root.findViewById(R.id.right_button);
button.setOnClickListener(detail);
((TextView)button).setText(getText(detail.mManageIntent != null
? R.string.service_manage : R.string.service_stop));
root.findViewById(R.id.left_button).setVisibility(View.INVISIBLE);
mActiveDetails.add(detail);
} }
boolean didProcess = false;
for (int i=-1; i<mMergedItem.mOtherProcesses.size(); i++) { for (int i=-1; i<mMergedItem.mOtherProcesses.size(); i++) {
RunningState.ProcessItem pi = i < 0 ? mMergedItem.mProcess RunningState.ProcessItem pi = i < 0 ? mMergedItem.mProcess
: mMergedItem.mOtherProcesses.get(i); : mMergedItem.mOtherProcesses.get(i);
@@ -238,70 +329,7 @@ public class RunningServiceDetails extends Activity {
continue; continue;
} }
if (!didProcess) { addProcessDetailsView(pi, i < 0);
mProcessesHeader = (TextView)mInflater.inflate(R.layout.separator_label,
mAllDetails, false);
mProcessesHeader.setText(R.string.runningservicedetails_processes_title);
mAllDetails.addView(mProcessesHeader);
didProcess = true;
}
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 (i < 0) {
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(i);
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);
} }
} }
} }

View File

@@ -47,18 +47,34 @@ import java.util.List;
*/ */
public class RunningState { public class RunningState {
final SparseArray<HashMap<String, ProcessItem>> mProcesses // Processes that are hosting a service we are interested in, organized
// by uid and name. Note that this mapping does not change even across
// service restarts, and during a restart there will still be a process
// entry.
final SparseArray<HashMap<String, ProcessItem>> mServiceProcessesByName
= new SparseArray<HashMap<String, ProcessItem>>(); = new SparseArray<HashMap<String, ProcessItem>>();
final SparseArray<ProcessItem> mActiveProcesses
// Processes that are hosting a service we are interested in, organized
// by their pid. These disappear and re-appear as services are restarted.
final SparseArray<ProcessItem> mServiceProcessesByPid
= new SparseArray<ProcessItem>(); = new SparseArray<ProcessItem>();
// Used to sort the interesting processes.
final ServiceProcessComparator mServiceProcessComparator final ServiceProcessComparator mServiceProcessComparator
= new ServiceProcessComparator(); = new ServiceProcessComparator();
// Temporary for finding process dependencies. // Additional heavy-weight processes to be shown to the user, even if
// there is no service running in them.
final ArrayList<ProcessItem> mHeavyProcesses = new ArrayList<ProcessItem>();
// All currently running processes, for finding dependencies etc.
final SparseArray<ProcessItem> mRunningProcesses final SparseArray<ProcessItem> mRunningProcesses
= new SparseArray<ProcessItem>(); = new SparseArray<ProcessItem>();
// The processes associated with services, in sorted order.
final ArrayList<ProcessItem> mProcessItems = new ArrayList<ProcessItem>(); final ArrayList<ProcessItem> mProcessItems = new ArrayList<ProcessItem>();
// All processes, used for retrieving memory information.
final ArrayList<ProcessItem> mAllProcessItems = new ArrayList<ProcessItem>(); final ArrayList<ProcessItem> mAllProcessItems = new ArrayList<ProcessItem>();
int mSequence = 0; int mSequence = 0;
@@ -128,6 +144,8 @@ public class RunningState {
int mRunningSeq; int mRunningSeq;
ActivityManager.RunningAppProcessInfo mRunningProcessInfo; ActivityManager.RunningAppProcessInfo mRunningProcessInfo;
MergedItem mMergedItem;
// Purely for sorting. // Purely for sorting.
boolean mIsSystem; boolean mIsSystem;
boolean mIsStarted; boolean mIsStarted;
@@ -435,10 +453,10 @@ public class RunningState {
continue; continue;
} }
HashMap<String, ProcessItem> procs = mProcesses.get(si.uid); HashMap<String, ProcessItem> procs = mServiceProcessesByName.get(si.uid);
if (procs == null) { if (procs == null) {
procs = new HashMap<String, ProcessItem>(); procs = new HashMap<String, ProcessItem>();
mProcesses.put(si.uid, procs); mServiceProcessesByName.put(si.uid, procs);
} }
ProcessItem proc = procs.get(si.process); ProcessItem proc = procs.get(si.process);
if (proc == null) { if (proc == null) {
@@ -453,10 +471,10 @@ public class RunningState {
changed = true; changed = true;
if (proc.mPid != pid) { if (proc.mPid != pid) {
if (proc.mPid != 0) { if (proc.mPid != 0) {
mActiveProcesses.remove(proc.mPid); mServiceProcessesByPid.remove(proc.mPid);
} }
if (pid != 0) { if (pid != 0) {
mActiveProcesses.put(pid, proc); mServiceProcessesByPid.put(pid, proc);
} }
proc.mPid = pid; proc.mPid = pid;
} }
@@ -474,19 +492,30 @@ public class RunningState {
final int NP = processes != null ? processes.size() : 0; final int NP = processes != null ? processes.size() : 0;
for (int i=0; i<NP; i++) { for (int i=0; i<NP; i++) {
ActivityManager.RunningAppProcessInfo pi = processes.get(i); ActivityManager.RunningAppProcessInfo pi = processes.get(i);
ProcessItem proc = mActiveProcesses.get(pi.pid); ProcessItem proc = mServiceProcessesByPid.get(pi.pid);
if (proc == null) { if (proc == null) {
// This process is not one that is a direct container // This process is not one that is a direct container
// of a service, so look for it in the secondary // of a service, so look for it in the secondary
// running list. // running list.
proc = mRunningProcesses.get(pi.pid); proc = mRunningProcesses.get(pi.pid);
if (proc == null) { if (proc == null) {
changed = true;
proc = new ProcessItem(context, pi.uid, pi.processName); proc = new ProcessItem(context, pi.uid, pi.processName);
proc.mPid = pi.pid; proc.mPid = pi.pid;
mRunningProcesses.put(pi.pid, proc); mRunningProcesses.put(pi.pid, proc);
} }
proc.mDependentProcesses.clear(); proc.mDependentProcesses.clear();
} }
if ((pi.flags&ActivityManager.RunningAppProcessInfo.FLAG_HEAVY_WEIGHT) != 0) {
if (!mHeavyProcesses.contains(proc)) {
changed = true;
mHeavyProcesses.add(proc);
}
proc.mCurSeq = mSequence;
proc.ensureLabel(pm);
}
proc.mRunningSeq = mSequence; proc.mRunningSeq = mSequence;
proc.mRunningProcessInfo = pi; proc.mRunningProcessInfo = pi;
} }
@@ -499,7 +528,7 @@ public class RunningState {
if (proc.mRunningSeq == mSequence) { if (proc.mRunningSeq == mSequence) {
int clientPid = proc.mRunningProcessInfo.importanceReasonPid; int clientPid = proc.mRunningProcessInfo.importanceReasonPid;
if (clientPid != 0) { if (clientPid != 0) {
ProcessItem client = mActiveProcesses.get(clientPid); ProcessItem client = mServiceProcessesByPid.get(clientPid);
if (client == null) { if (client == null) {
client = mRunningProcesses.get(clientPid); client = mRunningProcesses.get(clientPid);
} }
@@ -508,29 +537,42 @@ public class RunningState {
} }
} else { } else {
// In this pass the process doesn't have a client. // In this pass the process doesn't have a client.
// Clear to make sure if it later gets the same one // Clear to make sure that, if it later gets the same one,
// that we will detect the change. // we will detect the change.
proc.mClient = null; proc.mClient = null;
} }
} else { } else {
changed = true;
mRunningProcesses.remove(mRunningProcesses.keyAt(i)); mRunningProcesses.remove(mRunningProcesses.keyAt(i));
} }
} }
// Remove any old heavy processes.
int NHP = mHeavyProcesses.size();
for (int i=0; i<NHP; i++) {
ProcessItem proc = mHeavyProcesses.get(i);
if (mRunningProcesses.get(proc.mPid) == null) {
changed = true;
mHeavyProcesses.remove(i);
i--;
NHP--;
}
}
// Follow the tree from all primary service processes to all // Follow the tree from all primary service processes to all
// processes they are dependent on, marking these processes as // processes they are dependent on, marking these processes as
// still being active and determining if anything has changed. // still being active and determining if anything has changed.
final int NAP = mActiveProcesses.size(); final int NAP = mServiceProcessesByPid.size();
for (int i=0; i<NAP; i++) { for (int i=0; i<NAP; i++) {
ProcessItem proc = mActiveProcesses.valueAt(i); ProcessItem proc = mServiceProcessesByPid.valueAt(i);
if (proc.mCurSeq == mSequence) { if (proc.mCurSeq == mSequence) {
changed |= proc.buildDependencyChain(context, pm, mSequence); changed |= proc.buildDependencyChain(context, pm, mSequence);
} }
} }
// Look for services and their primary processes that no longer exist... // Look for services and their primary processes that no longer exist...
for (int i=0; i<mProcesses.size(); i++) { for (int i=0; i<mServiceProcessesByName.size(); i++) {
HashMap<String, ProcessItem> procs = mProcesses.valueAt(i); HashMap<String, ProcessItem> procs = mServiceProcessesByName.valueAt(i);
Iterator<ProcessItem> pit = procs.values().iterator(); Iterator<ProcessItem> pit = procs.values().iterator();
while (pit.hasNext()) { while (pit.hasNext()) {
ProcessItem pi = pit.next(); ProcessItem pi = pit.next();
@@ -545,10 +587,10 @@ public class RunningState {
changed = true; changed = true;
pit.remove(); pit.remove();
if (procs.size() == 0) { if (procs.size() == 0) {
mProcesses.remove(mProcesses.keyAt(i)); mServiceProcessesByName.remove(mServiceProcessesByName.keyAt(i));
} }
if (pi.mPid != 0) { if (pi.mPid != 0) {
mActiveProcesses.remove(pi.mPid); mServiceProcessesByPid.remove(pi.mPid);
} }
continue; continue;
} }
@@ -566,8 +608,8 @@ public class RunningState {
if (changed) { if (changed) {
// First determine an order for the services. // First determine an order for the services.
ArrayList<ProcessItem> sortedProcesses = new ArrayList<ProcessItem>(); ArrayList<ProcessItem> sortedProcesses = new ArrayList<ProcessItem>();
for (int i=0; i<mProcesses.size(); i++) { for (int i=0; i<mServiceProcessesByName.size(); i++) {
for (ProcessItem pi : mProcesses.valueAt(i).values()) { for (ProcessItem pi : mServiceProcessesByName.valueAt(i).values()) {
pi.mIsSystem = false; pi.mIsSystem = false;
pi.mIsStarted = true; pi.mIsStarted = true;
pi.mActiveSince = Long.MAX_VALUE; pi.mActiveSince = Long.MAX_VALUE;
@@ -643,6 +685,23 @@ public class RunningState {
mergedItem.update(context); mergedItem.update(context);
newMergedItems.add(mergedItem); newMergedItems.add(mergedItem);
} }
// Finally, heavy-weight processes need to be shown and will
// go at the top.
NHP = mHeavyProcesses.size();
for (int i=0; i<NHP; i++) {
ProcessItem proc = mHeavyProcesses.get(i);
if (proc.mClient == null && proc.mServices.size() <= 0) {
if (proc.mMergedItem == null) {
proc.mMergedItem = new MergedItem();
proc.mMergedItem.mProcess = proc;
}
proc.mMergedItem.update(context);
newMergedItems.add(0, proc.mMergedItem);
mProcessItems.add(proc);
}
}
synchronized (mLock) { synchronized (mLock) {
mItems = newItems; mItems = newItems;
mMergedItems = newMergedItems; mMergedItems = newMergedItems;

View File

@@ -288,9 +288,9 @@ public class BatteryHistoryChart extends View {
} }
void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath, void finishPaths(int w, int h, int levelh, int startX, int y, Path curLevelPath,
int lastBatX, boolean lastCharging, boolean lastScreenOn, Path lastPath) { int lastX, boolean lastCharging, boolean lastScreenOn, Path lastPath) {
if (curLevelPath != null) { if (curLevelPath != null) {
if (lastBatX >= 0) { if (lastX >= 0 && lastX < w) {
if (lastPath != null) { if (lastPath != null) {
lastPath.lineTo(w, y); lastPath.lineTo(w, y);
} }
@@ -301,10 +301,10 @@ public class BatteryHistoryChart extends View {
curLevelPath.close(); curLevelPath.close();
} }
if (lastCharging) { if (lastCharging && lastX < w) {
mChargingPath.lineTo(w, h-mChargingOffset); mChargingPath.lineTo(w, h-mChargingOffset);
} }
if (lastScreenOn) { if (lastScreenOn && lastX < w) {
mScreenOnPath.lineTo(w, h-mScreenOnOffset); mScreenOnPath.lineTo(w, h-mScreenOnOffset);
} }
} }
@@ -329,7 +329,7 @@ public class BatteryHistoryChart extends View {
final int levelh = h - mLevelOffset; final int levelh = h - mLevelOffset;
BatteryStats.HistoryItem rec = mHistFirst; BatteryStats.HistoryItem rec = mHistFirst;
int x = 0, y = 0, startX = 0, lastX = -1, lastY = -1, lastBatX = -1; int x = 0, y = 0, startX = 0, lastX = -1, lastY = -1;
int i = 0; int i = 0;
Path curLevelPath = null; Path curLevelPath = null;
Path lastLinePath = null; Path lastLinePath = null;
@@ -342,11 +342,8 @@ public class BatteryHistoryChart extends View {
if (lastX != x) { if (lastX != x) {
// We have moved by at least a pixel. // We have moved by at least a pixel.
if (lastY == y) { if (lastY != y) {
// Battery level is still the same; don't plot, // Don't plot changes within a pixel.
// but remember it.
lastBatX = x;
} else {
Path path; Path path;
byte value = rec.batteryLevel; byte value = rec.batteryLevel;
if (value <= BATTERY_CRITICAL) path = mBatCriticalPath; if (value <= BATTERY_CRITICAL) path = mBatCriticalPath;
@@ -372,7 +369,6 @@ public class BatteryHistoryChart extends View {
} }
lastX = x; lastX = x;
lastY = y; lastY = y;
lastBatX = -1;
final boolean charging = final boolean charging =
(rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0; (rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
@@ -399,9 +395,9 @@ public class BatteryHistoryChart extends View {
} }
} else if (curLevelPath != null) { } else if (curLevelPath != null) {
finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastBatX, finishPaths(x+1, h, levelh, startX, lastY, curLevelPath, lastX,
lastCharging, lastScreenOn, lastLinePath); lastCharging, lastScreenOn, lastLinePath);
lastX = lastY = lastBatX = -1; lastX = lastY = -1;
curLevelPath = null; curLevelPath = null;
lastLinePath = null; lastLinePath = null;
lastCharging = lastScreenOn = false; lastCharging = lastScreenOn = false;
@@ -411,7 +407,7 @@ public class BatteryHistoryChart extends View {
i++; i++;
} }
finishPaths(w, h, levelh, startX, lastY, curLevelPath, lastBatX, finishPaths(w, h, levelh, startX, lastY, curLevelPath, lastX,
lastCharging, lastScreenOn, lastLinePath); lastCharging, lastScreenOn, lastLinePath);
} }