Files
packages_apps_Settings/src/com/android/settings/applications/RunningState.java
Alan Viverette 0ba89bd54c Update Settings to use themed Context.getDrawable()
Explicit null theme is passed when using Resources.getDrawable() and
no theme is available, e.g. when using getResourcesForApplication().
This fixes an issue with ic_text_dot theming and helps avoid similar
issues in the future.

BUG: 17648301
Change-Id: I3e97c3490b6f2a55744f567b21284f2935ae9af7
2014-10-10 10:58:58 -07:00

1424 lines
57 KiB
Java

/*
* Copyright (C) 2010 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 com.android.settings.R;
import com.android.settings.Utils;
import android.app.ActivityManager;
import android.app.ActivityManagerNative;
import android.app.ActivityThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable.ConstantState;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.text.format.Formatter;
import android.util.Log;
import android.util.SparseArray;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
/**
* Singleton for retrieving and monitoring the state about all running
* applications/processes/services.
*/
public class RunningState {
static final String TAG = "RunningState";
static final boolean DEBUG_COMPARE = false;
static Object sGlobalLock = new Object();
static RunningState sInstance;
static final int MSG_RESET_CONTENTS = 1;
static final int MSG_UPDATE_CONTENTS = 2;
static final int MSG_REFRESH_UI = 3;
static final int MSG_UPDATE_TIME = 4;
static final long TIME_UPDATE_DELAY = 1000;
static final long CONTENTS_UPDATE_DELAY = 2000;
static final int MAX_SERVICES = 100;
final Context mApplicationContext;
final ActivityManager mAm;
final PackageManager mPm;
final UserManager mUm;
final int mMyUserId;
OnRefreshUiListener mRefreshUiListener;
final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges();
// 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>>();
// 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();
// Additional interesting processes to be shown to the user, even if
// there is no service running in them.
final ArrayList<ProcessItem> mInterestingProcesses = 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>();
// If there are other users on the device, these are the merged items
// representing all items that would be put in mMergedItems for that user.
final SparseArray<MergedItem> mOtherUserMergedItems = new SparseArray<MergedItem>();
// If there are other users on the device, these are the merged items
// representing all items that would be put in mUserBackgroundItems for that user.
final SparseArray<MergedItem> mOtherUserBackgroundItems = new SparseArray<MergedItem>();
// Tracking of information about users.
final SparseArray<UserState> mUsers = new SparseArray<UserState>();
static class AppProcessInfo {
final ActivityManager.RunningAppProcessInfo info;
boolean hasServices;
boolean hasForegroundServices;
AppProcessInfo(ActivityManager.RunningAppProcessInfo _info) {
info = _info;
}
}
// Temporary structure used when updating above information.
final SparseArray<AppProcessInfo> mTmpAppProcesses = new SparseArray<AppProcessInfo>();
int mSequence = 0;
final Comparator<RunningState.MergedItem> mBackgroundComparator
= new Comparator<RunningState.MergedItem>() {
@Override
public int compare(MergedItem lhs, MergedItem rhs) {
if (DEBUG_COMPARE) {
Log.i(TAG, "Comparing " + lhs + " with " + rhs);
Log.i(TAG, " Proc " + lhs.mProcess + " with " + rhs.mProcess);
Log.i(TAG, " UserId " + lhs.mUserId + " with " + rhs.mUserId);
}
if (lhs.mUserId != rhs.mUserId) {
if (lhs.mUserId == mMyUserId) return -1;
if (rhs.mUserId == mMyUserId) return 1;
return lhs.mUserId < rhs.mUserId ? -1 : 1;
}
if (lhs.mProcess == rhs.mProcess) {
if (lhs.mLabel == rhs.mLabel) {
return 0;
}
return lhs.mLabel != null ? lhs.mLabel.compareTo(rhs.mLabel) : -1;
}
if (lhs.mProcess == null) return -1;
if (rhs.mProcess == null) return 1;
if (DEBUG_COMPARE) Log.i(TAG, " Label " + lhs.mProcess.mLabel
+ " with " + rhs.mProcess.mLabel);
final ActivityManager.RunningAppProcessInfo lhsInfo
= lhs.mProcess.mRunningProcessInfo;
final ActivityManager.RunningAppProcessInfo rhsInfo
= rhs.mProcess.mRunningProcessInfo;
final boolean lhsBg = lhsInfo.importance
>= ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
final boolean rhsBg = rhsInfo.importance
>= ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND;
if (DEBUG_COMPARE) Log.i(TAG, " Bg " + lhsBg + " with " + rhsBg);
if (lhsBg != rhsBg) {
return lhsBg ? 1 : -1;
}
final boolean lhsA = (lhsInfo.flags
& ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES) != 0;
final boolean rhsA = (rhsInfo.flags
& ActivityManager.RunningAppProcessInfo.FLAG_HAS_ACTIVITIES) != 0;
if (DEBUG_COMPARE) Log.i(TAG, " Act " + lhsA + " with " + rhsA);
if (lhsA != rhsA) {
return lhsA ? -1 : 1;
}
if (DEBUG_COMPARE) Log.i(TAG, " Lru " + lhsInfo.lru + " with " + rhsInfo.lru);
if (lhsInfo.lru != rhsInfo.lru) {
return lhsInfo.lru < rhsInfo.lru ? -1 : 1;
}
if (lhs.mProcess.mLabel == rhs.mProcess.mLabel) {
return 0;
}
if (lhs.mProcess.mLabel == null) return 1;
if (rhs.mProcess.mLabel == null) return -1;
return lhs.mProcess.mLabel.compareTo(rhs.mProcess.mLabel);
}
};
// ----- following protected by mLock -----
// Lock for protecting the state that will be shared between the
// background update thread and the UI thread.
final Object mLock = new Object();
boolean mResumed;
boolean mHaveData;
boolean mWatchingBackgroundItems;
ArrayList<BaseItem> mItems = new ArrayList<BaseItem>();
ArrayList<MergedItem> mMergedItems = new ArrayList<MergedItem>();
ArrayList<MergedItem> mBackgroundItems = new ArrayList<MergedItem>();
ArrayList<MergedItem> mUserBackgroundItems = new ArrayList<MergedItem>();
int mNumBackgroundProcesses;
long mBackgroundProcessMemory;
int mNumForegroundProcesses;
long mForegroundProcessMemory;
int mNumServiceProcesses;
long mServiceProcessMemory;
// ----- BACKGROUND MONITORING THREAD -----
final HandlerThread mBackgroundThread;
final class BackgroundHandler extends Handler {
public BackgroundHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_RESET_CONTENTS:
reset();
break;
case MSG_UPDATE_CONTENTS:
synchronized (mLock) {
if (!mResumed) {
return;
}
}
Message cmd = mHandler.obtainMessage(MSG_REFRESH_UI);
cmd.arg1 = update(mApplicationContext, mAm) ? 1 : 0;
mHandler.sendMessage(cmd);
removeMessages(MSG_UPDATE_CONTENTS);
msg = obtainMessage(MSG_UPDATE_CONTENTS);
sendMessageDelayed(msg, CONTENTS_UPDATE_DELAY);
break;
}
}
};
final BackgroundHandler mBackgroundHandler;
final Handler mHandler = new Handler() {
int mNextUpdate = OnRefreshUiListener.REFRESH_TIME;
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_REFRESH_UI:
mNextUpdate = msg.arg1 != 0
? OnRefreshUiListener.REFRESH_STRUCTURE
: OnRefreshUiListener.REFRESH_DATA;
break;
case MSG_UPDATE_TIME:
synchronized (mLock) {
if (!mResumed) {
return;
}
}
removeMessages(MSG_UPDATE_TIME);
Message m = obtainMessage(MSG_UPDATE_TIME);
sendMessageDelayed(m, TIME_UPDATE_DELAY);
if (mRefreshUiListener != null) {
//Log.i("foo", "Refresh UI: " + mNextUpdate
// + " @ " + SystemClock.uptimeMillis());
mRefreshUiListener.onRefreshUi(mNextUpdate);
mNextUpdate = OnRefreshUiListener.REFRESH_TIME;
}
break;
}
}
};
// ----- DATA STRUCTURES -----
static interface OnRefreshUiListener {
public static final int REFRESH_TIME = 0;
public static final int REFRESH_DATA = 1;
public static final int REFRESH_STRUCTURE = 2;
public void onRefreshUi(int what);
}
static class UserState {
UserInfo mInfo;
String mLabel;
Drawable mIcon;
}
static class BaseItem {
final boolean mIsProcess;
final int mUserId;
PackageItemInfo mPackageInfo;
CharSequence mDisplayLabel;
String mLabel;
String mDescription;
int mCurSeq;
long mActiveSince;
long mSize;
String mSizeStr;
String mCurSizeStr;
boolean mNeedDivider;
boolean mBackground;
public BaseItem(boolean isProcess, int userId) {
mIsProcess = isProcess;
mUserId = userId;
}
public Drawable loadIcon(Context context, RunningState state) {
if (mPackageInfo != null) {
return mPackageInfo.loadIcon(state.mPm);
}
return null;
}
}
static class ServiceItem extends BaseItem {
ActivityManager.RunningServiceInfo mRunningService;
ServiceInfo mServiceInfo;
boolean mShownAsStarted;
MergedItem mMergedItem;
public ServiceItem(int userId) {
super(false, userId);
}
}
static class ProcessItem extends BaseItem {
final HashMap<ComponentName, ServiceItem> mServices
= new HashMap<ComponentName, ServiceItem>();
final SparseArray<ProcessItem> mDependentProcesses
= new SparseArray<ProcessItem>();
final int mUid;
final String mProcessName;
int mPid;
ProcessItem mClient;
int mLastNumDependentProcesses;
int mRunningSeq;
ActivityManager.RunningAppProcessInfo mRunningProcessInfo;
MergedItem mMergedItem;
boolean mInteresting;
// Purely for sorting.
boolean mIsSystem;
boolean mIsStarted;
long mActiveSince;
public ProcessItem(Context context, int uid, String processName) {
super(true, UserHandle.getUserId(uid));
mDescription = context.getResources().getString(
R.string.service_process_name, processName);
mUid = uid;
mProcessName = processName;
}
void ensureLabel(PackageManager pm) {
if (mLabel != null) {
return;
}
try {
ApplicationInfo ai = pm.getApplicationInfo(mProcessName,
PackageManager.GET_UNINSTALLED_PACKAGES);
if (ai.uid == mUid) {
mDisplayLabel = ai.loadLabel(pm);
mLabel = mDisplayLabel.toString();
mPackageInfo = ai;
return;
}
} catch (PackageManager.NameNotFoundException e) {
}
// If we couldn't get information about the overall
// process, try to find something about the uid.
String[] pkgs = pm.getPackagesForUid(mUid);
// If there is one package with this uid, that is what we want.
if (pkgs.length == 1) {
try {
ApplicationInfo ai = pm.getApplicationInfo(pkgs[0],
PackageManager.GET_UNINSTALLED_PACKAGES);
mDisplayLabel = ai.loadLabel(pm);
mLabel = mDisplayLabel.toString();
mPackageInfo = ai;
return;
} catch (PackageManager.NameNotFoundException e) {
}
}
// If there are multiple, see if one gives us the official name
// for this uid.
for (String name : pkgs) {
try {
PackageInfo pi = pm.getPackageInfo(name, 0);
if (pi.sharedUserLabel != 0) {
CharSequence nm = pm.getText(name,
pi.sharedUserLabel, pi.applicationInfo);
if (nm != null) {
mDisplayLabel = nm;
mLabel = nm.toString();
mPackageInfo = pi.applicationInfo;
return;
}
}
} catch (PackageManager.NameNotFoundException e) {
}
}
// If still don't have anything to display, just use the
// service info.
if (mServices.size() > 0) {
ApplicationInfo ai = mServices.values().iterator().next()
.mServiceInfo.applicationInfo;
mPackageInfo = ai;
mDisplayLabel = mPackageInfo.loadLabel(pm);
mLabel = mDisplayLabel.toString();
return;
}
// Finally... whatever, just pick the first package's name.
try {
ApplicationInfo ai = pm.getApplicationInfo(pkgs[0],
PackageManager.GET_UNINSTALLED_PACKAGES);
mDisplayLabel = ai.loadLabel(pm);
mLabel = mDisplayLabel.toString();
mPackageInfo = ai;
return;
} catch (PackageManager.NameNotFoundException e) {
}
}
boolean updateService(Context context, ActivityManager.RunningServiceInfo service) {
final PackageManager pm = context.getPackageManager();
boolean changed = false;
ServiceItem si = mServices.get(service.service);
if (si == null) {
changed = true;
si = new ServiceItem(mUserId);
si.mRunningService = service;
try {
si.mServiceInfo = ActivityThread.getPackageManager().getServiceInfo(
service.service, PackageManager.GET_UNINSTALLED_PACKAGES,
UserHandle.getUserId(service.uid));
if (si.mServiceInfo == null) {
Log.d("RunningService", "getServiceInfo returned null for: "
+ service.service);
return false;
}
} catch (RemoteException e) {
}
si.mDisplayLabel = makeLabel(pm,
si.mRunningService.service.getClassName(), si.mServiceInfo);
mLabel = mDisplayLabel != null ? mDisplayLabel.toString() : null;
si.mPackageInfo = si.mServiceInfo.applicationInfo;
mServices.put(service.service, si);
}
si.mCurSeq = mCurSeq;
si.mRunningService = service;
long activeSince = service.restarting == 0 ? service.activeSince : -1;
if (si.mActiveSince != activeSince) {
si.mActiveSince = activeSince;
changed = true;
}
if (service.clientPackage != null && service.clientLabel != 0) {
if (si.mShownAsStarted) {
si.mShownAsStarted = false;
changed = true;
}
try {
Resources clientr = pm.getResourcesForApplication(service.clientPackage);
String label = clientr.getString(service.clientLabel);
si.mDescription = context.getResources().getString(
R.string.service_client_name, label);
} catch (PackageManager.NameNotFoundException e) {
si.mDescription = null;
}
} else {
if (!si.mShownAsStarted) {
si.mShownAsStarted = true;
changed = true;
}
si.mDescription = context.getResources().getString(
R.string.service_started_by_app);
}
return changed;
}
boolean updateSize(Context context, long pss, int curSeq) {
mSize = pss * 1024;
if (mCurSeq == curSeq) {
String sizeStr = Formatter.formatShortFileSize(
context, mSize);
if (!sizeStr.equals(mSizeStr)){
mSizeStr = sizeStr;
// We update this on the second tick where we update just
// the text in the current items, so no need to say we
// changed here.
return false;
}
}
return false;
}
boolean buildDependencyChain(Context context, PackageManager pm, int curSeq) {
final int NP = mDependentProcesses.size();
boolean changed = false;
for (int i=0; i<NP; i++) {
ProcessItem proc = mDependentProcesses.valueAt(i);
if (proc.mClient != this) {
changed = true;
proc.mClient = this;
}
proc.mCurSeq = curSeq;
proc.ensureLabel(pm);
changed |= proc.buildDependencyChain(context, pm, curSeq);
}
if (mLastNumDependentProcesses != mDependentProcesses.size()) {
changed = true;
mLastNumDependentProcesses = mDependentProcesses.size();
}
return changed;
}
void addDependentProcesses(ArrayList<BaseItem> dest,
ArrayList<ProcessItem> destProc) {
final int NP = mDependentProcesses.size();
for (int i=0; i<NP; i++) {
ProcessItem proc = mDependentProcesses.valueAt(i);
proc.addDependentProcesses(dest, destProc);
dest.add(proc);
if (proc.mPid > 0) {
destProc.add(proc);
}
}
}
}
static class MergedItem extends BaseItem {
ProcessItem mProcess;
UserState mUser;
final ArrayList<ProcessItem> mOtherProcesses = new ArrayList<ProcessItem>();
final ArrayList<ServiceItem> mServices = new ArrayList<ServiceItem>();
final ArrayList<MergedItem> mChildren = new ArrayList<MergedItem>();
private int mLastNumProcesses = -1, mLastNumServices = -1;
MergedItem(int userId) {
super(false, userId);
}
private void setDescription(Context context, int numProcesses, int numServices) {
if (mLastNumProcesses != numProcesses || mLastNumServices != numServices) {
mLastNumProcesses = numProcesses;
mLastNumServices = numServices;
int resid = R.string.running_processes_item_description_s_s;
if (numProcesses != 1) {
resid = numServices != 1
? R.string.running_processes_item_description_p_p
: R.string.running_processes_item_description_p_s;
} else if (numServices != 1) {
resid = R.string.running_processes_item_description_s_p;
}
mDescription = context.getResources().getString(resid, numProcesses,
numServices);
}
}
boolean update(Context context, boolean background) {
mBackground = background;
if (mUser != null) {
// This is a merged item that contains a child collection
// of items... that is, it is an entire user, containing
// everything associated with that user. So set it up as such.
// For concrete stuff we need about the process of this item,
// we will just use the info from the first child.
MergedItem child0 = mChildren.get(0);
mPackageInfo = child0.mProcess.mPackageInfo;
mLabel = mUser != null ? mUser.mLabel : null;
mDisplayLabel = mLabel;
int numProcesses = 0;
int numServices = 0;
mActiveSince = -1;
for (int i=0; i<mChildren.size(); i++) {
MergedItem child = mChildren.get(i);
numProcesses += child.mLastNumProcesses;
numServices += child.mLastNumServices;
if (child.mActiveSince >= 0 && mActiveSince < child.mActiveSince) {
mActiveSince = child.mActiveSince;
}
}
if (!mBackground) {
setDescription(context, numProcesses, numServices);
}
} else {
mPackageInfo = mProcess.mPackageInfo;
mDisplayLabel = mProcess.mDisplayLabel;
mLabel = mProcess.mLabel;
if (!mBackground) {
setDescription(context, (mProcess.mPid > 0 ? 1 : 0) + mOtherProcesses.size(),
mServices.size());
}
mActiveSince = -1;
for (int i=0; i<mServices.size(); i++) {
ServiceItem si = mServices.get(i);
if (si.mActiveSince >= 0 && mActiveSince < si.mActiveSince) {
mActiveSince = si.mActiveSince;
}
}
}
return false;
}
boolean updateSize(Context context) {
if (mUser != null) {
mSize = 0;
for (int i=0; i<mChildren.size(); i++) {
MergedItem child = mChildren.get(i);
child.updateSize(context);
mSize += child.mSize;
}
} else {
mSize = mProcess.mSize;
for (int i=0; i<mOtherProcesses.size(); i++) {
mSize += mOtherProcesses.get(i).mSize;
}
}
String sizeStr = Formatter.formatShortFileSize(
context, mSize);
if (!sizeStr.equals(mSizeStr)){
mSizeStr = sizeStr;
// We update this on the second tick where we update just
// the text in the current items, so no need to say we
// changed here.
return false;
}
return false;
}
public Drawable loadIcon(Context context, RunningState state) {
if (mUser == null) {
return super.loadIcon(context, state);
}
if (mUser.mIcon != null) {
ConstantState constState = mUser.mIcon.getConstantState();
if (constState == null) {
return mUser.mIcon;
} else {
return constState.newDrawable();
}
}
return context.getDrawable(
com.android.internal.R.drawable.ic_menu_cc);
}
}
class ServiceProcessComparator implements Comparator<ProcessItem> {
public int compare(ProcessItem object1, ProcessItem object2) {
if (object1.mUserId != object2.mUserId) {
if (object1.mUserId == mMyUserId) return -1;
if (object2.mUserId == mMyUserId) return 1;
return object1.mUserId < object2.mUserId ? -1 : 1;
}
if (object1.mIsStarted != object2.mIsStarted) {
// Non-started processes go last.
return object1.mIsStarted ? -1 : 1;
}
if (object1.mIsSystem != object2.mIsSystem) {
// System processes go below non-system.
return object1.mIsSystem ? 1 : -1;
}
if (object1.mActiveSince != object2.mActiveSince) {
// Remaining ones are sorted with the longest running
// services last.
return (object1.mActiveSince > object2.mActiveSince) ? -1 : 1;
}
return 0;
}
}
static CharSequence makeLabel(PackageManager pm,
String className, PackageItemInfo item) {
if (item != null && (item.labelRes != 0
|| item.nonLocalizedLabel != null)) {
CharSequence label = item.loadLabel(pm);
if (label != null) {
return label;
}
}
String label = className;
int tail = label.lastIndexOf('.');
if (tail >= 0) {
label = label.substring(tail+1, label.length());
}
return label;
}
static RunningState getInstance(Context context) {
synchronized (sGlobalLock) {
if (sInstance == null) {
sInstance = new RunningState(context);
}
return sInstance;
}
}
private RunningState(Context context) {
mApplicationContext = context.getApplicationContext();
mAm = (ActivityManager)mApplicationContext.getSystemService(Context.ACTIVITY_SERVICE);
mPm = mApplicationContext.getPackageManager();
mUm = (UserManager)mApplicationContext.getSystemService(Context.USER_SERVICE);
mMyUserId = UserHandle.myUserId();
mResumed = false;
mBackgroundThread = new HandlerThread("RunningState:Background");
mBackgroundThread.start();
mBackgroundHandler = new BackgroundHandler(mBackgroundThread.getLooper());
}
void resume(OnRefreshUiListener listener) {
synchronized (mLock) {
mResumed = true;
mRefreshUiListener = listener;
if (mInterestingConfigChanges.applyNewConfig(mApplicationContext.getResources())) {
mHaveData = false;
mBackgroundHandler.removeMessages(MSG_RESET_CONTENTS);
mBackgroundHandler.removeMessages(MSG_UPDATE_CONTENTS);
mBackgroundHandler.sendEmptyMessage(MSG_RESET_CONTENTS);
}
if (!mBackgroundHandler.hasMessages(MSG_UPDATE_CONTENTS)) {
mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS);
}
mHandler.sendEmptyMessage(MSG_UPDATE_TIME);
}
}
void updateNow() {
synchronized (mLock) {
mBackgroundHandler.removeMessages(MSG_UPDATE_CONTENTS);
mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS);
}
}
boolean hasData() {
synchronized (mLock) {
return mHaveData;
}
}
void waitForData() {
synchronized (mLock) {
while (!mHaveData) {
try {
mLock.wait(0);
} catch (InterruptedException e) {
}
}
}
}
void pause() {
synchronized (mLock) {
mResumed = false;
mRefreshUiListener = null;
mHandler.removeMessages(MSG_UPDATE_TIME);
}
}
private boolean isInterestingProcess(ActivityManager.RunningAppProcessInfo pi) {
if ((pi.flags&ActivityManager.RunningAppProcessInfo.FLAG_CANT_SAVE_STATE) != 0) {
return true;
}
if ((pi.flags&ActivityManager.RunningAppProcessInfo.FLAG_PERSISTENT) == 0
&& pi.importance >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
&& pi.importance < ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE
&& pi.importanceReasonCode
== ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN) {
return true;
}
return false;
}
private void reset() {
mServiceProcessesByName.clear();
mServiceProcessesByPid.clear();
mInterestingProcesses.clear();
mRunningProcesses.clear();
mProcessItems.clear();
mAllProcessItems.clear();
mUsers.clear();
}
private void addOtherUserItem(Context context, ArrayList<MergedItem> newMergedItems,
SparseArray<MergedItem> userItems, MergedItem newItem) {
MergedItem userItem = userItems.get(newItem.mUserId);
boolean first = userItem == null || userItem.mCurSeq != mSequence;
if (first) {
if (userItem == null) {
userItem = new MergedItem(newItem.mUserId);
userItems.put(newItem.mUserId, userItem);
} else {
userItem.mChildren.clear();
}
userItem.mCurSeq = mSequence;
if ((userItem.mUser=mUsers.get(newItem.mUserId)) == null) {
userItem.mUser = new UserState();
UserInfo info = mUm.getUserInfo(newItem.mUserId);
userItem.mUser.mInfo = info;
if (info != null) {
userItem.mUser.mIcon = Utils.getUserIcon(context, mUm, info);
}
String name = info != null ? info.name : null;
if (name == null && info != null) {
name = Integer.toString(info.id);
} else if (info == null) {
name = context.getString(R.string.unknown);
}
userItem.mUser.mLabel = context.getResources().getString(
R.string.running_process_item_user_label, name);
}
newMergedItems.add(userItem);
}
userItem.mChildren.add(newItem);
}
private boolean update(Context context, ActivityManager am) {
final PackageManager pm = context.getPackageManager();
mSequence++;
boolean changed = false;
// Retrieve list of services, filtering out anything that definitely
// won't be shown in the UI.
List<ActivityManager.RunningServiceInfo> services
= am.getRunningServices(MAX_SERVICES);
int NS = services != null ? services.size() : 0;
for (int i=0; i<NS; i++) {
ActivityManager.RunningServiceInfo si = services.get(i);
// We are not interested in services that have not been started
// and don't have a known client, because
// there is nothing the user can do about them.
if (!si.started && si.clientLabel == 0) {
services.remove(i);
i--;
NS--;
continue;
}
// We likewise don't care about services running in a
// persistent process like the system or phone.
if ((si.flags&ActivityManager.RunningServiceInfo.FLAG_PERSISTENT_PROCESS)
!= 0) {
services.remove(i);
i--;
NS--;
continue;
}
}
// Retrieve list of running processes, organizing them into a sparse
// array for easy retrieval.
List<ActivityManager.RunningAppProcessInfo> processes
= am.getRunningAppProcesses();
final int NP = processes != null ? processes.size() : 0;
mTmpAppProcesses.clear();
for (int i=0; i<NP; i++) {
ActivityManager.RunningAppProcessInfo pi = processes.get(i);
mTmpAppProcesses.put(pi.pid, new AppProcessInfo(pi));
}
// Initial iteration through running services to collect per-process
// info about them.
for (int i=0; i<NS; i++) {
ActivityManager.RunningServiceInfo si = services.get(i);
if (si.restarting == 0 && si.pid > 0) {
AppProcessInfo ainfo = mTmpAppProcesses.get(si.pid);
if (ainfo != null) {
ainfo.hasServices = true;
if (si.foreground) {
ainfo.hasForegroundServices = true;
}
}
}
}
// Update state we are maintaining about process that are running services.
for (int i=0; i<NS; i++) {
ActivityManager.RunningServiceInfo si = services.get(i);
// If this service's process is in use at a higher importance
// due to another process bound to one of its services, then we
// won't put it in the top-level list of services. Instead we
// want it to be included in the set of processes that the other
// process needs.
if (si.restarting == 0 && si.pid > 0) {
AppProcessInfo ainfo = mTmpAppProcesses.get(si.pid);
if (ainfo != null && !ainfo.hasForegroundServices) {
// This process does not have any foreground services.
// If its importance is greater than the service importance
// then there is something else more significant that is
// keeping it around that it should possibly be included as
// a part of instead of being shown by itself.
if (ainfo.info.importance
< ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE) {
// Follow process chain to see if there is something
// else that could be shown
boolean skip = false;
ainfo = mTmpAppProcesses.get(ainfo.info.importanceReasonPid);
while (ainfo != null) {
if (ainfo.hasServices || isInterestingProcess(ainfo.info)) {
skip = true;
break;
}
ainfo = mTmpAppProcesses.get(ainfo.info.importanceReasonPid);
}
if (skip) {
continue;
}
}
}
}
HashMap<String, ProcessItem> procs = mServiceProcessesByName.get(si.uid);
if (procs == null) {
procs = new HashMap<String, ProcessItem>();
mServiceProcessesByName.put(si.uid, procs);
}
ProcessItem proc = procs.get(si.process);
if (proc == null) {
changed = true;
proc = new ProcessItem(context, si.uid, si.process);
procs.put(si.process, proc);
}
if (proc.mCurSeq != mSequence) {
int pid = si.restarting == 0 ? si.pid : 0;
if (pid != proc.mPid) {
changed = true;
if (proc.mPid != pid) {
if (proc.mPid != 0) {
mServiceProcessesByPid.remove(proc.mPid);
}
if (pid != 0) {
mServiceProcessesByPid.put(pid, proc);
}
proc.mPid = pid;
}
}
proc.mDependentProcesses.clear();
proc.mCurSeq = mSequence;
}
changed |= proc.updateService(context, si);
}
// Now update the map of other processes that are running (but
// don't have services actively running inside them).
for (int i=0; i<NP; i++) {
ActivityManager.RunningAppProcessInfo pi = processes.get(i);
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 (isInterestingProcess(pi)) {
if (!mInterestingProcesses.contains(proc)) {
changed = true;
mInterestingProcesses.add(proc);
}
proc.mCurSeq = mSequence;
proc.mInteresting = true;
proc.ensureLabel(pm);
} else {
proc.mInteresting = false;
}
proc.mRunningSeq = mSequence;
proc.mRunningProcessInfo = pi;
}
// Build the chains from client processes to the process they are
// dependent on; also remove any old running processes.
int NRP = mRunningProcesses.size();
for (int i = 0; i < NRP;) {
ProcessItem proc = mRunningProcesses.valueAt(i);
if (proc.mRunningSeq == mSequence) {
int clientPid = proc.mRunningProcessInfo.importanceReasonPid;
if (clientPid != 0) {
ProcessItem client = mServiceProcessesByPid.get(clientPid);
if (client == null) {
client = mRunningProcesses.get(clientPid);
}
if (client != null) {
client.mDependentProcesses.put(proc.mPid, proc);
}
} else {
// In this pass the process doesn't have a client.
// Clear to make sure that, if it later gets the same one,
// we will detect the change.
proc.mClient = null;
}
i++;
} else {
changed = true;
mRunningProcesses.remove(mRunningProcesses.keyAt(i));
NRP--;
}
}
// Remove any old interesting processes.
int NHP = mInterestingProcesses.size();
for (int i=0; i<NHP; i++) {
ProcessItem proc = mInterestingProcesses.get(i);
if (!proc.mInteresting || mRunningProcesses.get(proc.mPid) == null) {
changed = true;
mInterestingProcesses.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 = mServiceProcessesByPid.size();
for (int i=0; i<NAP; 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...
ArrayList<Integer> uidToDelete = null;
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();
if (pi.mCurSeq == mSequence) {
pi.ensureLabel(pm);
if (pi.mPid == 0) {
// Sanity: a non-process can't be dependent on
// anything.
pi.mDependentProcesses.clear();
}
} else {
changed = true;
pit.remove();
if (procs.size() == 0) {
if (uidToDelete == null) {
uidToDelete = new ArrayList<Integer>();
}
uidToDelete.add(mServiceProcessesByName.keyAt(i));
}
if (pi.mPid != 0) {
mServiceProcessesByPid.remove(pi.mPid);
}
continue;
}
Iterator<ServiceItem> sit = pi.mServices.values().iterator();
while (sit.hasNext()) {
ServiceItem si = sit.next();
if (si.mCurSeq != mSequence) {
changed = true;
sit.remove();
}
}
}
}
if (uidToDelete != null) {
for (int i = 0; i < uidToDelete.size(); i++) {
int uid = uidToDelete.get(i);
mServiceProcessesByName.remove(uid);
}
}
if (changed) {
// First determine an order for the services.
ArrayList<ProcessItem> sortedProcesses = new ArrayList<ProcessItem>();
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;
for (ServiceItem si : pi.mServices.values()) {
if (si.mServiceInfo != null
&& (si.mServiceInfo.applicationInfo.flags
& ApplicationInfo.FLAG_SYSTEM) != 0) {
pi.mIsSystem = true;
}
if (si.mRunningService != null
&& si.mRunningService.clientLabel != 0) {
pi.mIsStarted = false;
if (pi.mActiveSince > si.mRunningService.activeSince) {
pi.mActiveSince = si.mRunningService.activeSince;
}
}
}
sortedProcesses.add(pi);
}
}
Collections.sort(sortedProcesses, mServiceProcessComparator);
ArrayList<BaseItem> newItems = new ArrayList<BaseItem>();
ArrayList<MergedItem> newMergedItems = new ArrayList<MergedItem>();
SparseArray<MergedItem> otherUsers = null;
mProcessItems.clear();
for (int i=0; i<sortedProcesses.size(); i++) {
ProcessItem pi = sortedProcesses.get(i);
pi.mNeedDivider = false;
int firstProc = mProcessItems.size();
// First add processes we are dependent on.
pi.addDependentProcesses(newItems, mProcessItems);
// And add the process itself.
newItems.add(pi);
if (pi.mPid > 0) {
mProcessItems.add(pi);
}
// Now add the services running in it.
MergedItem mergedItem = null;
boolean haveAllMerged = false;
boolean needDivider = false;
for (ServiceItem si : pi.mServices.values()) {
si.mNeedDivider = needDivider;
needDivider = true;
newItems.add(si);
if (si.mMergedItem != null) {
if (mergedItem != null && mergedItem != si.mMergedItem) {
haveAllMerged = false;
}
mergedItem = si.mMergedItem;
} else {
haveAllMerged = false;
}
}
if (!haveAllMerged || mergedItem == null
|| mergedItem.mServices.size() != pi.mServices.size()) {
// Whoops, we need to build a new MergedItem!
mergedItem = new MergedItem(pi.mUserId);
for (ServiceItem si : pi.mServices.values()) {
mergedItem.mServices.add(si);
si.mMergedItem = mergedItem;
}
mergedItem.mProcess = pi;
mergedItem.mOtherProcesses.clear();
for (int mpi=firstProc; mpi<(mProcessItems.size()-1); mpi++) {
mergedItem.mOtherProcesses.add(mProcessItems.get(mpi));
}
}
mergedItem.update(context, false);
if (mergedItem.mUserId != mMyUserId) {
addOtherUserItem(context, newMergedItems, mOtherUserMergedItems, mergedItem);
} else {
newMergedItems.add(mergedItem);
}
}
// Finally, interesting processes need to be shown and will
// go at the top.
NHP = mInterestingProcesses.size();
for (int i=0; i<NHP; i++) {
ProcessItem proc = mInterestingProcesses.get(i);
if (proc.mClient == null && proc.mServices.size() <= 0) {
if (proc.mMergedItem == null) {
proc.mMergedItem = new MergedItem(proc.mUserId);
proc.mMergedItem.mProcess = proc;
}
proc.mMergedItem.update(context, false);
if (proc.mMergedItem.mUserId != mMyUserId) {
addOtherUserItem(context, newMergedItems, mOtherUserMergedItems,
proc.mMergedItem);
} else {
newMergedItems.add(0, proc.mMergedItem);
}
mProcessItems.add(proc);
}
}
// Finally finally, user aggregated merged items need to be
// updated now that they have all of their children.
final int NU = mOtherUserMergedItems.size();
for (int i=0; i<NU; i++) {
MergedItem user = mOtherUserMergedItems.valueAt(i);
if (user.mCurSeq == mSequence) {
user.update(context, false);
}
}
synchronized (mLock) {
mItems = newItems;
mMergedItems = newMergedItems;
}
}
// Count number of interesting other (non-active) processes, and
// build a list of all processes we will retrieve memory for.
mAllProcessItems.clear();
mAllProcessItems.addAll(mProcessItems);
int numBackgroundProcesses = 0;
int numForegroundProcesses = 0;
int numServiceProcesses = 0;
NRP = mRunningProcesses.size();
for (int i=0; i<NRP; i++) {
ProcessItem proc = mRunningProcesses.valueAt(i);
if (proc.mCurSeq != mSequence) {
// We didn't hit this process as a dependency on one
// of our active ones, so add it up if needed.
if (proc.mRunningProcessInfo.importance >=
ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
numBackgroundProcesses++;
mAllProcessItems.add(proc);
} else if (proc.mRunningProcessInfo.importance <=
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
numForegroundProcesses++;
mAllProcessItems.add(proc);
} else {
Log.i("RunningState", "Unknown non-service process: "
+ proc.mProcessName + " #" + proc.mPid);
}
} else {
numServiceProcesses++;
}
}
long backgroundProcessMemory = 0;
long foregroundProcessMemory = 0;
long serviceProcessMemory = 0;
ArrayList<MergedItem> newBackgroundItems = null;
ArrayList<MergedItem> newUserBackgroundItems = null;
boolean diffUsers = false;
try {
final int numProc = mAllProcessItems.size();
int[] pids = new int[numProc];
for (int i=0; i<numProc; i++) {
pids[i] = mAllProcessItems.get(i).mPid;
}
long[] pss = ActivityManagerNative.getDefault()
.getProcessPss(pids);
int bgIndex = 0;
for (int i=0; i<pids.length; i++) {
ProcessItem proc = mAllProcessItems.get(i);
changed |= proc.updateSize(context, pss[i], mSequence);
if (proc.mCurSeq == mSequence) {
serviceProcessMemory += proc.mSize;
} else if (proc.mRunningProcessInfo.importance >=
ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
backgroundProcessMemory += proc.mSize;
MergedItem mergedItem;
if (newBackgroundItems != null) {
mergedItem = proc.mMergedItem = new MergedItem(proc.mUserId);
proc.mMergedItem.mProcess = proc;
diffUsers |= mergedItem.mUserId != mMyUserId;
newBackgroundItems.add(mergedItem);
} else {
if (bgIndex >= mBackgroundItems.size()
|| mBackgroundItems.get(bgIndex).mProcess != proc) {
newBackgroundItems = new ArrayList<MergedItem>(numBackgroundProcesses);
for (int bgi=0; bgi<bgIndex; bgi++) {
mergedItem = mBackgroundItems.get(bgi);
diffUsers |= mergedItem.mUserId != mMyUserId;
newBackgroundItems.add(mergedItem);
}
mergedItem = proc.mMergedItem = new MergedItem(proc.mUserId);
proc.mMergedItem.mProcess = proc;
diffUsers |= mergedItem.mUserId != mMyUserId;
newBackgroundItems.add(mergedItem);
} else {
mergedItem = mBackgroundItems.get(bgIndex);
}
}
mergedItem.update(context, true);
mergedItem.updateSize(context);
bgIndex++;
} else if (proc.mRunningProcessInfo.importance <=
ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
foregroundProcessMemory += proc.mSize;
}
}
} catch (RemoteException e) {
}
if (newBackgroundItems == null) {
// One or more at the bottom may no longer exist.
if (mBackgroundItems.size() > numBackgroundProcesses) {
newBackgroundItems = new ArrayList<MergedItem>(numBackgroundProcesses);
for (int bgi=0; bgi<numBackgroundProcesses; bgi++) {
MergedItem mergedItem = mBackgroundItems.get(bgi);
diffUsers |= mergedItem.mUserId != mMyUserId;
newBackgroundItems.add(mergedItem);
}
}
}
if (newBackgroundItems != null) {
// The background items have changed; we need to re-build the
// per-user items.
if (!diffUsers) {
// Easy: there are no other users, we can just use the same array.
newUserBackgroundItems = newBackgroundItems;
} else {
// We now need to re-build the per-user list so that background
// items for users are collapsed together.
newUserBackgroundItems = new ArrayList<MergedItem>();
final int NB = newBackgroundItems.size();
for (int i=0; i<NB; i++) {
MergedItem mergedItem = newBackgroundItems.get(i);
if (mergedItem.mUserId != mMyUserId) {
addOtherUserItem(context, newUserBackgroundItems,
mOtherUserBackgroundItems, mergedItem);
} else {
newUserBackgroundItems.add(mergedItem);
}
}
// And user aggregated merged items need to be
// updated now that they have all of their children.
final int NU = mOtherUserBackgroundItems.size();
for (int i=0; i<NU; i++) {
MergedItem user = mOtherUserBackgroundItems.valueAt(i);
if (user.mCurSeq == mSequence) {
user.update(context, true);
user.updateSize(context);
}
}
}
}
for (int i=0; i<mMergedItems.size(); i++) {
mMergedItems.get(i).updateSize(context);
}
synchronized (mLock) {
mNumBackgroundProcesses = numBackgroundProcesses;
mNumForegroundProcesses = numForegroundProcesses;
mNumServiceProcesses = numServiceProcesses;
mBackgroundProcessMemory = backgroundProcessMemory;
mForegroundProcessMemory = foregroundProcessMemory;
mServiceProcessMemory = serviceProcessMemory;
if (newBackgroundItems != null) {
mBackgroundItems = newBackgroundItems;
mUserBackgroundItems = newUserBackgroundItems;
if (mWatchingBackgroundItems) {
changed = true;
}
}
if (!mHaveData) {
mHaveData = true;
mLock.notifyAll();
}
}
return changed;
}
ArrayList<BaseItem> getCurrentItems() {
synchronized (mLock) {
return mItems;
}
}
void setWatchingBackgroundItems(boolean watching) {
synchronized (mLock) {
mWatchingBackgroundItems = watching;
}
}
ArrayList<MergedItem> getCurrentMergedItems() {
synchronized (mLock) {
return mMergedItems;
}
}
ArrayList<MergedItem> getCurrentBackgroundItems() {
synchronized (mLock) {
return mUserBackgroundItems;
}
}
}