/* * Copyright (C) 2006 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; import com.android.settings.R; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.AlertDialog; import android.app.Dialog; import android.app.ListActivity; import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; 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.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; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.text.format.DateUtils; import android.text.format.Formatter; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.LayoutInflater; import android.view.View; 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; import java.io.FileInputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.List; public class RunningServices extends ListActivity implements AbsListView.RecyclerListener, DialogInterface.OnClickListener { static final String TAG = "RunningServices"; /** Maximum number of services to retrieve */ static final int MAX_SERVICES = 100; static final int MSG_UPDATE_TIMES = 1; static final int MSG_UPDATE_CONTENTS = 2; static final int MSG_REFRESH_UI = 3; static final long TIME_UPDATE_DELAY = 1000; static final long CONTENTS_UPDATE_DELAY = 2000; // Memory pages are 4K. static final long PAGE_SIZE = 4*1024; long SECONDARY_SERVER_MEM; final HashMap<View, ActiveItem> mActiveItems = new HashMap<View, ActiveItem>(); ActivityManager mAm; State mState; StringBuilder mBuilder = new StringBuilder(128); BaseItem mCurSelected; 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; byte[] mBuffer = new byte[1024]; class ActiveItem { View mRootView; BaseItem mItem; ActivityManager.RunningServiceInfo mService; ViewHolder mHolder; long mFirstRunTime; void updateTime(Context context) { if (mItem.mIsProcess) { String size = mItem.mSizeStr != null ? mItem.mSizeStr : ""; if (!size.equals(mItem.mCurSizeStr)) { mItem.mCurSizeStr = size; mHolder.size.setText(size); } } else { if (mItem.mActiveSince >= 0) { mHolder.size.setText(DateUtils.formatElapsedTime(mBuilder, (SystemClock.uptimeMillis()-mFirstRunTime)/1000)); } else { mHolder.size.setText(context.getResources().getText( R.string.service_restarting)); } } } } static class BaseItem { final boolean mIsProcess; PackageItemInfo mPackageInfo; CharSequence mDisplayLabel; String mLabel; String mDescription; int mCurSeq; long mActiveSince; long mSize; String mSizeStr; String mCurSizeStr; boolean mNeedDivider; public BaseItem(boolean isProcess) { mIsProcess = isProcess; } } static class ServiceItem extends BaseItem { ActivityManager.RunningServiceInfo mRunningService; ServiceInfo mServiceInfo; boolean mShownAsStarted; public ServiceItem() { super(false); } } 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; // Purely for sorting. boolean mIsSystem; boolean mIsStarted; long mActiveSince; public ProcessItem(Context context, int uid, String processName) { super(true); 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, 0); 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], 0); 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) { mPackageInfo = mServices.values().iterator().next() .mServiceInfo.applicationInfo; mDisplayLabel = mPackageInfo.loadLabel(pm); mLabel = mDisplayLabel.toString(); return; } // Finally... whatever, just pick the first package's name. try { ApplicationInfo ai = pm.getApplicationInfo(pkgs[0], 0); 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(); si.mRunningService = service; try { si.mServiceInfo = pm.getServiceInfo(service.service, 0); } catch (PackageManager.NameNotFoundException e) { } if (si.mServiceInfo != null && (si.mServiceInfo.labelRes != 0 || si.mServiceInfo.nonLocalizedLabel != null)) { si.mDisplayLabel = si.mServiceInfo.loadLabel(pm); si.mLabel = si.mDisplayLabel.toString(); } else { si.mLabel = si.mRunningService.service.getClassName(); int tail = si.mLabel.lastIndexOf('.'); if (tail >= 0) { si.mLabel = si.mLabel.substring(tail+1, si.mLabel.length()); } si.mDisplayLabel = si.mLabel; } 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, Debug.MemoryInfo mem, int curSeq) { mSize = ((long)mem.getTotalPss()) * 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 ServiceProcessComparator implements Comparator<ProcessItem> { public int compare(ProcessItem object1, ProcessItem object2) { 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 class State { final SparseArray<HashMap<String, ProcessItem>> mProcesses = new SparseArray<HashMap<String, ProcessItem>>(); final SparseArray<ProcessItem> mActiveProcesses = new SparseArray<ProcessItem>(); final ServiceProcessComparator mServiceProcessComparator = new ServiceProcessComparator(); // Temporary for finding process dependencies. final SparseArray<ProcessItem> mRunningProcesses = new SparseArray<ProcessItem>(); final ArrayList<ProcessItem> mProcessItems = new ArrayList<ProcessItem>(); final ArrayList<ProcessItem> mAllProcessItems = new ArrayList<ProcessItem>(); int mSequence = 0; // ----- 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(); ArrayList<BaseItem> mItems = new ArrayList<BaseItem>(); int mNumBackgroundProcesses; long mBackgroundProcessMemory; int mNumForegroundProcesses; long mForegroundProcessMemory; int mNumServiceProcesses; long mServiceProcessMemory; boolean update(Context context, ActivityManager am) { final PackageManager pm = context.getPackageManager(); mSequence++; boolean changed = false; List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(MAX_SERVICES); final 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) { 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) { continue; } HashMap<String, ProcessItem> procs = mProcesses.get(si.uid); if (procs == null) { procs = new HashMap<String, ProcessItem>(); mProcesses.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) { mActiveProcesses.remove(proc.mPid); } if (pid != 0) { mActiveProcesses.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). List<ActivityManager.RunningAppProcessInfo> processes = am.getRunningAppProcesses(); 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); 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) { proc = new ProcessItem(context, pi.uid, pi.processName); proc.mPid = pi.pid; mRunningProcesses.put(pi.pid, proc); } proc.mDependentProcesses.clear(); } 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; i++) { ProcessItem proc = mRunningProcesses.valueAt(i); if (proc.mRunningSeq == mSequence) { int clientPid = proc.mRunningProcessInfo.importanceReasonPid; if (clientPid != 0) { ProcessItem client = mActiveProcesses.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 if it later gets the same one // that we will detect the change. proc.mClient = null; } } else { mRunningProcesses.remove(mRunningProcesses.keyAt(i)); } } // 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(); for (int i=0; i<NAP; i++) { ProcessItem proc = mActiveProcesses.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); 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) { mProcesses.remove(mProcesses.keyAt(i)); } if (pi.mPid != 0) { mActiveProcesses.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 (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()) { 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>(); mProcessItems.clear(); for (int i=0; i<sortedProcesses.size(); i++) { ProcessItem pi = sortedProcesses.get(i); pi.mNeedDivider = false; // 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); } // And finally the services running in it. boolean needDivider = false; for (ServiceItem si : pi.mServices.values()) { si.mNeedDivider = needDivider; needDivider = true; newItems.add(si); } } synchronized (mLock) { mItems = newItems; } } // 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(TAG, "Unknown non-service process: " + proc.mProcessName + " #" + proc.mPid); } } else { numServiceProcesses++; } } long backgroundProcessMemory = 0; long foregroundProcessMemory = 0; long serviceProcessMemory = 0; try { final int numProc = mAllProcessItems.size(); int[] pids = new int[numProc]; for (int i=0; i<numProc; i++) { pids[i] = mAllProcessItems.get(i).mPid; } Debug.MemoryInfo[] mem = ActivityManagerNative.getDefault() .getProcessMemoryInfo(pids); for (int i=pids.length-1; i>=0; i--) { ProcessItem proc = mAllProcessItems.get(i); changed |= proc.updateSize(context, mem[i], mSequence); if (proc.mCurSeq == mSequence) { serviceProcessMemory += proc.mSize; } else if (proc.mRunningProcessInfo.importance >= ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) { backgroundProcessMemory += proc.mSize; } else if (proc.mRunningProcessInfo.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) { foregroundProcessMemory += proc.mSize; } } } catch (RemoteException e) { } synchronized (mLock) { mNumBackgroundProcesses = numBackgroundProcesses; mNumForegroundProcesses = numForegroundProcesses; mNumServiceProcesses = numServiceProcesses; mBackgroundProcessMemory = backgroundProcessMemory; mForegroundProcessMemory = foregroundProcessMemory; mServiceProcessMemory = serviceProcessMemory; } return changed; } ArrayList<BaseItem> getCurrentItems() { synchronized (mLock) { return mItems; } } } static class TimeTicker extends TextView { public TimeTicker(Context context, AttributeSet attrs) { super(context, attrs); } } static class ViewHolder { ImageView separator; ImageView icon; TextView name; TextView description; TextView size; } class ServiceListAdapter extends BaseAdapter { final State mState; final LayoutInflater mInflater; ArrayList<BaseItem> mItems; ServiceListAdapter(State state) { mState = state; mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); refreshItems(); } void refreshItems() { ArrayList<BaseItem> newItems = mState.getCurrentItems(); if (mItems != newItems) { mItems = newItems; } if (mItems == null) { mItems = new ArrayList<BaseItem>(); } } public boolean hasStableIds() { return true; } public int getCount() { return mItems.size(); } public Object getItem(int position) { return mItems.get(position); } public long getItemId(int position) { return mItems.get(position).hashCode(); } public boolean areAllItemsEnabled() { return false; } public boolean isEnabled(int position) { return !mItems.get(position).mIsProcess; } public View getView(int position, View convertView, ViewGroup parent) { View v; if (convertView == null) { v = newView(parent); } else { v = convertView; } bindView(v, position); return v; } public View newView(ViewGroup parent) { View v = mInflater.inflate(R.layout.running_services_item, parent, false); ViewHolder h = new ViewHolder(); h.separator = (ImageView)v.findViewById(R.id.separator); h.icon = (ImageView)v.findViewById(R.id.icon); h.name = (TextView)v.findViewById(R.id.name); h.description = (TextView)v.findViewById(R.id.description); h.size = (TextView)v.findViewById(R.id.size); v.setTag(h); return v; } public void bindView(View view, int position) { synchronized (mState.mLock) { ViewHolder vh = (ViewHolder) view.getTag(); if (position >= mItems.size()) { // List must have changed since we last reported its // size... ignore here, we will be doing a data changed // to refresh the entire list. return; } BaseItem item = mItems.get(position); vh.name.setText(item.mDisplayLabel); vh.separator.setVisibility(item.mNeedDivider ? View.VISIBLE : View.INVISIBLE); ActiveItem ai = new ActiveItem(); ai.mRootView = view; ai.mItem = item; ai.mHolder = vh; ai.mFirstRunTime = item.mActiveSince; vh.description.setText(item.mDescription); if (item.mIsProcess) { view.setBackgroundColor(mProcessBgColor); vh.icon.setImageDrawable(null); vh.icon.setVisibility(View.GONE); vh.description.setText(item.mDescription); item.mCurSizeStr = null; } else { view.setBackgroundDrawable(null); vh.icon.setImageDrawable(item.mPackageInfo.loadIcon(getPackageManager())); vh.icon.setVisibility(View.VISIBLE); vh.description.setText(item.mDescription); ai.mFirstRunTime = item.mActiveSince; } ai.updateTime(RunningServices.this); mActiveItems.put(view, ai); } } } 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); } } } HandlerThread mBackgroundThread; final class BackgroundHandler extends Handler { public BackgroundHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_UPDATE_CONTENTS: Message cmd = mHandler.obtainMessage(MSG_REFRESH_UI); cmd.arg1 = mState.update(RunningServices.this, mAm) ? 1 : 0; mHandler.sendMessage(cmd); removeMessages(MSG_UPDATE_CONTENTS); msg = obtainMessage(MSG_UPDATE_CONTENTS); sendMessageDelayed(msg, CONTENTS_UPDATE_DELAY); break; } } }; BackgroundHandler mBackgroundHandler; final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_UPDATE_TIMES: Iterator<ActiveItem> it = mActiveItems.values().iterator(); while (it.hasNext()) { ActiveItem ai = it.next(); if (ai.mRootView.getWindowToken() == null) { // Clean out any dead views, just in case. it.remove(); continue; } ai.updateTime(RunningServices.this); } removeMessages(MSG_UPDATE_TIMES); msg = obtainMessage(MSG_UPDATE_TIMES); sendMessageDelayed(msg, TIME_UPDATE_DELAY); break; case MSG_REFRESH_UI: refreshUi(msg.arg1 != 0); break; } } }; private boolean matchText(byte[] buffer, int index, String text) { int N = text.length(); if ((index+N) >= buffer.length) { return false; } for (int i=0; i<N; i++) { if (buffer[index+i] != text.charAt(i)) { return false; } } return true; } private long extractMemValue(byte[] buffer, int index) { while (index < buffer.length && buffer[index] != '\n') { if (buffer[index] >= '0' && buffer[index] <= '9') { int start = index; index++; while (index < buffer.length && buffer[index] >= '0' && buffer[index] <= '9') { index++; } String str = new String(buffer, 0, start, index-start); return ((long)Integer.parseInt(str)) * 1024; } index++; } return 0; } private long readAvailMem() { try { long memFree = 0; long memCached = 0; FileInputStream is = new FileInputStream("/proc/meminfo"); int len = is.read(mBuffer); is.close(); final int BUFLEN = mBuffer.length; for (int i=0; i<len && (memFree == 0 || memCached == 0); i++) { if (matchText(mBuffer, i, "MemFree")) { i += 7; memFree = extractMemValue(mBuffer, i); } else if (matchText(mBuffer, i, "Cached")) { i += 6; memCached = extractMemValue(mBuffer, i); } while (i < BUFLEN && mBuffer[i] != '\n') { i++; } } return memFree + memCached; } catch (java.io.FileNotFoundException e) { } catch (java.io.IOException e) { } return 0; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mAm = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); mState = (State)getLastNonConfigurationInstance(); if (mState == null) { mState = new State(); } mProcessBgColor = 0xff505050; 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); // Magic! Implementation detail! Don't count on this! SECONDARY_SERVER_MEM = Integer.valueOf(SystemProperties.get("ro.SECONDARY_SERVER_MEM"))*PAGE_SIZE; } void refreshUi(boolean dataChanged) { if (dataChanged) { ServiceListAdapter adapter = (ServiceListAdapter)(getListView().getAdapter()); adapter.refreshItems(); adapter.notifyDataSetChanged(); } // This is the amount of available memory until we start killing // background services. long availMem = readAvailMem() - SECONDARY_SERVER_MEM; if (availMem < 0) { availMem = 0; } synchronized (mState.mLock) { if (mLastNumBackgroundProcesses != mState.mNumBackgroundProcesses || mLastBackgroundProcessMemory != mState.mBackgroundProcessMemory || mLastAvailMemory != availMem) { mLastNumBackgroundProcesses = mState.mNumBackgroundProcesses; mLastBackgroundProcessMemory = mState.mBackgroundProcessMemory; mLastAvailMemory = availMem; String availStr = availMem != 0 ? Formatter.formatShortFileSize(this, availMem) : "0"; String sizeStr = Formatter.formatShortFileSize(this, mLastBackgroundProcessMemory); mBackgroundProcessText.setText(getResources().getString( R.string.service_background_processes, mLastNumBackgroundProcesses, availStr, sizeStr)); } if (mLastNumForegroundProcesses != mState.mNumForegroundProcesses || mLastForegroundProcessMemory != mState.mForegroundProcessMemory) { mLastNumForegroundProcesses = mState.mNumForegroundProcesses; mLastForegroundProcessMemory = mState.mForegroundProcessMemory; String sizeStr = Formatter.formatShortFileSize(this, mLastForegroundProcessMemory); mForegroundProcessText.setText(getResources().getString( R.string.service_foreground_processes, mLastNumForegroundProcesses, sizeStr)); } mLastNumServiceProcesses = mState.mNumServiceProcesses; mLastServiceProcessMemory = mState.mServiceProcessMemory; float totalMem = availMem + mLastBackgroundProcessMemory + mLastForegroundProcessMemory + mLastServiceProcessMemory; mColorBar.setRatios(mLastForegroundProcessMemory/totalMem, mLastServiceProcessMemory/totalMem, (availMem+mLastBackgroundProcessMemory)/totalMem); } } @Override protected void onListItemClick(ListView l, View v, int position, long id) { BaseItem bi = (BaseItem)l.getAdapter().getItem(position); if (!bi.mIsProcess) { ServiceItem si = (ServiceItem)bi; if (si.mRunningService.clientLabel != 0) { mCurSelected = null; PendingIntent pi = mAm.getRunningServiceControlPanel( si.mRunningService.service); if (pi != null) { try { this.startIntentSender(pi.getIntentSender(), null, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET, Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET, 0); } catch (IntentSender.SendIntentException e) { Log.w(TAG, e); } catch (IllegalArgumentException e) { Log.w(TAG, e); } catch (ActivityNotFoundException e) { Log.w(TAG, e); } } } else { mCurSelected = bi; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.confirm_stop_service); String msg = getResources().getString( R.string.confirm_stop_service_msg, si.mPackageInfo.loadLabel(getPackageManager())); builder.setMessage(msg); builder.setPositiveButton(R.string.confirm_stop_stop, this); builder.setNegativeButton(R.string.confirm_stop_cancel, null); builder.setCancelable(true); mCurDialog = builder.show(); } } else { mCurSelected = null; } } public void onClick(DialogInterface dialog, int which) { if (mCurSelected != null) { stopService(new Intent().setComponent( ((ServiceItem)mCurSelected).mRunningService.service)); if (mBackgroundHandler != null) { mBackgroundHandler.sendEmptyMessage(MSG_UPDATE_CONTENTS); } } } @Override protected void onPause() { super.onPause(); mHandler.removeMessages(MSG_UPDATE_TIMES); if (mBackgroundThread != null) { mBackgroundThread.quit(); mBackgroundThread = null; mBackgroundHandler = null; } } @Override protected void onResume() { super.onResume(); refreshUi(mState.update(this, mAm)); mBackgroundThread = new HandlerThread("RunningServices"); mBackgroundThread.start(); mBackgroundHandler = new BackgroundHandler(mBackgroundThread.getLooper()); mHandler.removeMessages(MSG_UPDATE_TIMES); Message msg = mHandler.obtainMessage(MSG_UPDATE_TIMES); mHandler.sendMessageDelayed(msg, TIME_UPDATE_DELAY); mBackgroundHandler.removeMessages(MSG_UPDATE_CONTENTS); msg = mBackgroundHandler.obtainMessage(MSG_UPDATE_CONTENTS); mBackgroundHandler.sendMessageDelayed(msg, CONTENTS_UPDATE_DELAY); } @Override public Object onRetainNonConfigurationInstance() { return mState; } public void onMovedToScrapHeap(View view) { mActiveItems.remove(view); } @Override protected void onDestroy() { super.onDestroy(); if (mCurDialog != null) { mCurDialog.dismiss(); } } }