/* * 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.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.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; 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.ListView; import android.widget.TextView; import java.util.ArrayList; 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 long TIME_UPDATE_DELAY = 1000; static final long CONTENTS_UPDATE_DELAY = 2000; final HashMap mActiveItems = new HashMap(); ActivityManager mAm; State mState; StringBuilder mBuilder = new StringBuilder(128); BaseItem mCurSelected; int mProcessBgColor; TextView mBackgroundProcessText; TextView mForegroundProcessText; int mLastNumBackgroundProcesses = -1; int mLastNumForegroundProcesses = -1; Dialog mCurDialog; 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 mServices = new HashMap(); final SparseArray mDependentProcesses = new SparseArray(); final int mUid; final String mProcessName; int mPid; ProcessItem mClient; int mLastNumDependentProcesses; int mRunningSeq; ActivityManager.RunningAppProcessInfo mRunningProcessInfo; public ProcessItem(int uid, String processName) { super(true); mDescription = 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; 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) { boolean changed = false; if (mPid != 0 && mSize == 0) { final int NP = mDependentProcesses.size(); for (int i=0; i dest) { final int NP = mDependentProcesses.size(); for (int i=0; i> mProcesses = new SparseArray>(); final SparseArray mActiveProcesses = new SparseArray(); // Temporary for finding process dependencies. final SparseArray mRunningProcesses = new SparseArray(); final ArrayList mItems = new ArrayList(); int mSequence = 0; int mNumBackgroundProcesses; int mNumForegroundProcesses; boolean update(Context context, ActivityManager am) { final PackageManager pm = context.getPackageManager(); mSequence++; boolean changed = false; List services = am.getRunningServices(MAX_SERVICES); final int NS = services != null ? services.size() : 0; for (int i=0; i procs = mProcesses.get(si.uid); if (procs == null) { procs = new HashMap(); mProcesses.put(si.uid, procs); } ProcessItem proc = procs.get(si.process); if (proc == null) { changed = true; proc = new ProcessItem(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.mSize = 0; 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 processes = am.getRunningAppProcesses(); final int NP = processes != null ? processes.size() : 0; for (int i=0; i= ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) { mNumBackgroundProcesses++; } else if (proc.mRunningProcessInfo.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE) { mNumForegroundProcesses++; } } } // Look for services and their primary processes that no longer exist... for (int i=0; i procs = mProcesses.valueAt(i); Iterator pit = procs.values().iterator(); while (pit.hasNext()) { ProcessItem pi = pit.next(); if (pi.mCurSeq == mSequence) { pi.ensureLabel(pm); changed |= pi.updateSize(context); 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 sit = pi.mServices.values().iterator(); while (sit.hasNext()) { ServiceItem si = sit.next(); if (si.mCurSeq != mSequence) { changed = true; sit.remove(); } } } } if (changed) { mItems.clear(); for (int i=0; i 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_UPDATE_CONTENTS: updateList(); removeMessages(MSG_UPDATE_CONTENTS); msg = obtainMessage(MSG_UPDATE_CONTENTS); sendMessageDelayed(msg, CONTENTS_UPDATE_DELAY); break; } } }; @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)); mBackgroundProcessText = (TextView)findViewById(R.id.backgroundText); mForegroundProcessText = (TextView)findViewById(R.id.foregroundText); } void updateList() { if (mState.update(this, mAm)) { ((ServiceListAdapter)(getListView().getAdapter())).notifyDataSetChanged(); } if (mLastNumBackgroundProcesses != mState.mNumBackgroundProcesses) { mLastNumBackgroundProcesses = mState.mNumBackgroundProcesses; mBackgroundProcessText.setText(getResources().getString( R.string.service_background_processes, mLastNumBackgroundProcesses)); } if (mLastNumForegroundProcesses != mState.mNumForegroundProcesses) { mLastNumForegroundProcesses = mState.mNumForegroundProcesses; mForegroundProcessText.setText(getResources().getString( R.string.service_foreground_processes, mLastNumForegroundProcesses)); } } @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.startActivity(pi, null, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET, Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); } catch (PendingIntent.CanceledException 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); builder.setMessage(R.string.confirm_stop_service_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)); updateList(); } } @Override protected void onPause() { super.onPause(); mHandler.removeMessages(MSG_UPDATE_TIMES); mHandler.removeMessages(MSG_UPDATE_CONTENTS); } @Override protected void onResume() { super.onResume(); updateList(); mHandler.removeMessages(MSG_UPDATE_TIMES); Message msg = mHandler.obtainMessage(MSG_UPDATE_TIMES); mHandler.sendMessageDelayed(msg, TIME_UPDATE_DELAY); mHandler.removeMessages(MSG_UPDATE_CONTENTS); msg = mHandler.obtainMessage(MSG_UPDATE_CONTENTS); mHandler.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(); } } }