<!-- Running service details, default description for services that are started. -->
<string name="service_stop_description">This service was started by its
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. -->
<string name="service_manage_description"><xliff:g id="client_name">%1$s</xliff:g>:
currently in use. Touch Settings to control it.</string>
uptimeView.setText(DateUtils.formatElapsedTime(builder,
(SystemClock.uptimeMillis()-mFirstRunTime)/1000));
} else {
- uptimeView.setText(context.getResources().getText(
- R.string.service_restarting));
+ boolean isService = false;
+ 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("");
+ }
}
}
}
RunningProcessesView.ActiveItem mSnippetActiveItem;
RunningProcessesView.ViewHolder mSnippetViewHolder;
+ int mNumServices, mNumProcesses;
+
TextView mServicesHeader;
TextView mProcessesHeader;
final ArrayList<ActiveDetail> mActiveDetails = new ArrayList<ActiveDetail>();
} catch (ActivityNotFoundException e) {
Log.w(TAG, e);
}
- } else {
+ } 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) {
mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS);
}
}
+ } else {
+ // Heavy-weight process. We'll do a force-stop on it.
+ mAm.forceStopPackage(mActiveItem.mItem.mPackageInfo.packageName);
+ finish();
}
}
}
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() {
for (int i=mActiveDetails.size()-1; i>=0; i--) {
mAllDetails.removeView(mActiveDetails.get(i).mRootView);
mProcessesHeader = null;
}
+ mNumServices = mNumProcesses = 0;
+
if (mMergedItem != null) {
for (int i=0; i<mMergedItem.mServices.size(); i++) {
- if (i == 0) {
- mServicesHeader = (TextView)mInflater.inflate(R.layout.separator_label,
- mAllDetails, false);
- mServicesHeader.setText(R.string.runningservicedetails_services_title);
- mAllDetails.addView(mServicesHeader);
- }
- RunningState.ServiceItem si = mMergedItem.mServices.get(i);
- 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, 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);
+ 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);
}
- boolean didProcess = false;
for (int i=-1; i<mMergedItem.mOtherProcesses.size(); i++) {
RunningState.ProcessItem pi = i < 0 ? mMergedItem.mProcess
: mMergedItem.mOtherProcesses.get(i);
continue;
}
- if (!didProcess) {
- 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);
+ addProcessDetailsView(pi, i < 0);
}
}
}
*/
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>>();
- 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>();
+
+ // Used to sort the interesting processes.
final ServiceProcessComparator mServiceProcessComparator
= 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
= new SparseArray<ProcessItem>();
+ // The processes associated with services, in sorted order.
final ArrayList<ProcessItem> mProcessItems = new ArrayList<ProcessItem>();
+
+ // All processes, used for retrieving memory information.
final ArrayList<ProcessItem> mAllProcessItems = new ArrayList<ProcessItem>();
int mSequence = 0;
int mRunningSeq;
ActivityManager.RunningAppProcessInfo mRunningProcessInfo;
+ MergedItem mMergedItem;
+
// Purely for sorting.
boolean mIsSystem;
boolean mIsStarted;
continue;
}
- HashMap<String, ProcessItem> procs = mProcesses.get(si.uid);
+ HashMap<String, ProcessItem> procs = mServiceProcessesByName.get(si.uid);
if (procs == null) {
procs = new HashMap<String, ProcessItem>();
- mProcesses.put(si.uid, procs);
+ mServiceProcessesByName.put(si.uid, procs);
}
ProcessItem proc = procs.get(si.process);
if (proc == null) {
changed = true;
if (proc.mPid != pid) {
if (proc.mPid != 0) {
- mActiveProcesses.remove(proc.mPid);
+ mServiceProcessesByPid.remove(proc.mPid);
}
if (pid != 0) {
- mActiveProcesses.put(pid, proc);
+ mServiceProcessesByPid.put(pid, proc);
}
proc.mPid = pid;
}
final int NP = processes != null ? processes.size() : 0;
for (int i=0; i<NP; i++) {
ActivityManager.RunningAppProcessInfo pi = processes.get(i);
- ProcessItem proc = mActiveProcesses.get(pi.pid);
+ ProcessItem proc = mServiceProcessesByPid.get(pi.pid);
if (proc == null) {
// This process is not one that is a direct container
// of a service, so look for it in the secondary
// running list.
proc = mRunningProcesses.get(pi.pid);
if (proc == null) {
+ changed = true;
proc = new ProcessItem(context, pi.uid, pi.processName);
proc.mPid = pi.pid;
mRunningProcesses.put(pi.pid, proc);
}
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.mRunningProcessInfo = pi;
}
if (proc.mRunningSeq == mSequence) {
int clientPid = proc.mRunningProcessInfo.importanceReasonPid;
if (clientPid != 0) {
- ProcessItem client = mActiveProcesses.get(clientPid);
+ ProcessItem client = mServiceProcessesByPid.get(clientPid);
if (client == null) {
client = mRunningProcesses.get(clientPid);
}
}
} else {
// In this pass the process doesn't have a client.
- // Clear to make sure if it later gets the same one
- // that we will detect the change.
+ // Clear to make sure that, if it later gets the same one,
+ // we will detect the change.
proc.mClient = null;
}
} else {
+ changed = true;
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
// processes they are dependent on, marking these processes as
// 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++) {
- ProcessItem proc = mActiveProcesses.valueAt(i);
+ ProcessItem proc = mServiceProcessesByPid.valueAt(i);
if (proc.mCurSeq == mSequence) {
changed |= proc.buildDependencyChain(context, pm, mSequence);
}
}
// Look for services and their primary processes that no longer exist...
- for (int i=0; i<mProcesses.size(); i++) {
- HashMap<String, ProcessItem> procs = mProcesses.valueAt(i);
+ for (int i=0; i<mServiceProcessesByName.size(); i++) {
+ HashMap<String, ProcessItem> procs = mServiceProcessesByName.valueAt(i);
Iterator<ProcessItem> pit = procs.values().iterator();
while (pit.hasNext()) {
ProcessItem pi = pit.next();
changed = true;
pit.remove();
if (procs.size() == 0) {
- mProcesses.remove(mProcesses.keyAt(i));
+ mServiceProcessesByName.remove(mServiceProcessesByName.keyAt(i));
}
if (pi.mPid != 0) {
- mActiveProcesses.remove(pi.mPid);
+ mServiceProcessesByPid.remove(pi.mPid);
}
continue;
}
if (changed) {
// First determine an order for the services.
ArrayList<ProcessItem> sortedProcesses = new ArrayList<ProcessItem>();
- for (int i=0; i<mProcesses.size(); i++) {
- for (ProcessItem pi : mProcesses.valueAt(i).values()) {
+ for (int i=0; i<mServiceProcessesByName.size(); i++) {
+ for (ProcessItem pi : mServiceProcessesByName.valueAt(i).values()) {
pi.mIsSystem = false;
pi.mIsStarted = true;
pi.mActiveSince = Long.MAX_VALUE;
mergedItem.update(context);
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) {
mItems = newItems;
mMergedItems = newMergedItems;
}
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 (lastBatX >= 0) {
+ if (lastX >= 0 && lastX < w) {
if (lastPath != null) {
lastPath.lineTo(w, y);
}
curLevelPath.close();
}
- if (lastCharging) {
+ if (lastCharging && lastX < w) {
mChargingPath.lineTo(w, h-mChargingOffset);
}
- if (lastScreenOn) {
+ if (lastScreenOn && lastX < w) {
mScreenOnPath.lineTo(w, h-mScreenOnOffset);
}
}
final int levelh = h - mLevelOffset;
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;
Path curLevelPath = null;
Path lastLinePath = null;
if (lastX != x) {
// We have moved by at least a pixel.
- if (lastY == y) {
- // Battery level is still the same; don't plot,
- // but remember it.
- lastBatX = x;
- } else {
+ if (lastY != y) {
+ // Don't plot changes within a pixel.
Path path;
byte value = rec.batteryLevel;
if (value <= BATTERY_CRITICAL) path = mBatCriticalPath;
}
lastX = x;
lastY = y;
- lastBatX = -1;
final boolean charging =
(rec.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) != 0;
}
} 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);
- lastX = lastY = lastBatX = -1;
+ lastX = lastY = -1;
curLevelPath = null;
lastLinePath = null;
lastCharging = lastScreenOn = false;
i++;
}
- finishPaths(w, h, levelh, startX, lastY, curLevelPath, lastBatX,
+ finishPaths(w, h, levelh, startX, lastY, curLevelPath, lastX,
lastCharging, lastScreenOn, lastLinePath);
}