From c58c549cc963bd8946d332a0f9945d9a1ed19e99 Mon Sep 17 00:00:00 2001 From: Dianne Hackborn Date: Mon, 31 Aug 2009 21:33:17 -0700 Subject: [PATCH] New UI for viewing and stopping currently running services. Change-Id: I86012262635c911be23513aa0b027174b490374d --- AndroidManifest.xml | 12 + res/layout/running_services.xml | 30 + res/layout/running_services_item.xml | 86 +++ res/values/strings.xml | 19 + res/xml/application_settings.xml | 8 + src/com/android/settings/RunningServices.java | 555 ++++++++++++++++++ 6 files changed, 710 insertions(+) create mode 100644 res/layout/running_services.xml create mode 100644 res/layout/running_services_item.xml create mode 100644 src/com/android/settings/RunningServices.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 9f6ac0e091b..d4f938161f9 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -279,6 +279,18 @@ + + + + + + + + + + + + + + + + diff --git a/res/layout/running_services_item.xml b/res/layout/running_services_item.xml new file mode 100644 index 00000000000..6748a1a1a04 --- /dev/null +++ b/res/layout/running_services_item.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index e1a9bcd1d31..2af9b187db5 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1403,6 +1403,25 @@ found in the list of installed applications. version %1$s + + Running services + + View and control currently running services + + Restarting + + No running services + + Stop service? + + The service will no longer run until + started again by its application. This may have undesireable + consequences. + + Stop + + Cancel + Language & keyboard diff --git a/res/xml/application_settings.xml b/res/xml/application_settings.xml index 8d0a7cb5153..b5418e810de 100644 --- a/res/xml/application_settings.xml +++ b/res/xml/application_settings.xml @@ -43,6 +43,14 @@ android:targetClass="com.android.settings.ManageApplications" /> + + + + diff --git a/src/com/android/settings/RunningServices.java b/src/com/android/settings/RunningServices.java new file mode 100644 index 00000000000..3340a4b3b42 --- /dev/null +++ b/src/com/android/settings/RunningServices.java @@ -0,0 +1,555 @@ +/* + * 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.ProgressDialog; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageStatsObserver; +import android.content.pm.PackageInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageStats; +import android.content.pm.ServiceInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; +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.Config; +import android.util.Log; +import android.util.SparseArray; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.AbsListView; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.Filter; +import android.widget.Filterable; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.AdapterView.OnItemClickListener; + +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; + + Dialog mCurDialog; + + class ActiveItem { + View mRootView; + BaseItem mItem; + ActivityManager.RunningServiceInfo mService; + ViewHolder mHolder; + long mFirstRunTime; + + void updateTime(Context context) { + 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 { + boolean mIsProcess; + PackageItemInfo mPackageInfo; + CharSequence mDisplayLabel; + String mLabel; + String mName; + + int mCurSeq; + + long mActiveSince; + long mSize; + String mSizeStr; + boolean mNeedDivider; + } + + static class ServiceItem extends BaseItem { + ActivityManager.RunningServiceInfo mRunningService; + ServiceInfo mServiceInfo; + } + + static class ProcessItem extends BaseItem { + final HashMap mServices + = new HashMap(); + int mUid; + int mPid; + + boolean updateService(PackageManager pm, + ActivityManager.RunningServiceInfo service) { + 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; + } + + return changed; + } + } + + static class State { + final SparseArray> mProcesses + = new SparseArray>(); + + final ArrayList mItems = new ArrayList(); + + int mSequence = 0; + + boolean update(Context context, ActivityManager am) { + final PackageManager pm = context.getPackageManager(); + + mSequence++; + + boolean changed = false; + + List services + = am.getRunningServices(MAX_SERVICES); + if (services == null) { + return false; + } + final int NS = services.size(); + 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(); + proc.mIsProcess = true; + proc.mName = si.process; + proc.mUid = si.uid; + try { + ApplicationInfo ai = pm.getApplicationInfo(si.process, 0); + if (ai.uid == si.uid) { + proc.mDisplayLabel = ai.loadLabel(context.getPackageManager()); + proc.mLabel = proc.mDisplayLabel.toString(); + proc.mPackageInfo = ai; + } + } catch (PackageManager.NameNotFoundException e) { + } + procs.put(si.process, proc); + } + + if (proc.mCurSeq != mSequence) { + int pid = si.restarting == 0 ? si.pid : 0; + if (pid != proc.mPid) { + changed = true; + proc.mPid = pid; + } + proc.mSize = 0; + if (proc.mPid != 0) { + Debug.MemoryInfo mi = new Debug.MemoryInfo(); + // XXX This is a hack... I really don't want to be + // doing a synchronous call into the app, but can't + // figure out any other way to get the pss. + try { + ActivityManagerNative.getDefault().getProcessMemoryInfo( + proc.mPid, mi); + proc.mSize = (mi.dalvikPss + mi.nativePss + + mi.otherPss) * 1024; + String sizeStr = Formatter.formatFileSize( + context, proc.mSize); + if (!sizeStr.equals(proc.mSizeStr)){ + changed = true; + proc.mSizeStr = sizeStr; + } + } catch (RemoteException e) { + } + } + proc.mCurSeq = mSequence; + } + changed |= proc.updateService(context.getPackageManager(), si); + + if (proc.mLabel == null) { + // If we couldn't get information about the overall + // process, try to find something about the uid. + String[] pkgs = pm.getPackagesForUid(proc.mUid); + 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) { + proc.mDisplayLabel = nm; + proc.mLabel = nm.toString(); + proc.mPackageInfo = pi.applicationInfo; + break; + } + } + } catch (PackageManager.NameNotFoundException e) { + } + } + + // If still don't have anything to display, just use the + // service info. + if (proc.mLabel == null) { + proc.mPackageInfo = proc.mServices.get(si.service) + .mServiceInfo.applicationInfo; + proc.mDisplayLabel = proc.mPackageInfo.loadLabel(pm); + proc.mLabel = proc.mDisplayLabel.toString(); + } + } + } + + // Look for anything that no longer exists... + 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) { + changed = true; + pit.remove(); + if (procs.size() == 0) { + mProcesses.remove(mProcesses.keyAt(i)); + } + 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