New UI for viewing and stopping currently running services.

Change-Id: I86012262635c911be23513aa0b027174b490374d
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 9f6ac0e..d4f9381 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -279,6 +279,18 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="RunningServices"
+                  android:label="@string/runningservices_settings_title"
+                  android:clearTaskOnLaunch="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.MONKEY" />
+                <category android:name="android.intent.category.VOICE_LAUNCH" />
+                <category android:name="com.android.settings.SHORTCUT" />
+            </intent-filter>
+        </activity>
+        
         <activity android:name="SecuritySettings" android:label="@string/security_settings_title"
                 android:configChanges="orientation|keyboardHidden"
                 android:clearTaskOnLaunch="true"
diff --git a/res/layout/running_services.xml b/res/layout/running_services.xml
new file mode 100644
index 0000000..8fe7f9f
--- /dev/null
+++ b/res/layout/running_services.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 
+ * Copyright (C) 2008 Google Inc.
+ *
+ * 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.
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+    <ListView android:id="@android:id/list"
+            android:layout_width="fill_parent" 
+            android:layout_height="fill_parent"
+            android:drawSelectorOnTop="false"
+            android:fastScrollEnabled="true" />
+    <TextView android:id="@android:id/empty"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:gravity="center"
+            android:text="@string/no_running_services"
+            android:textAppearance="?android:attr/textAppearanceLarge" />
+</merge>
diff --git a/res/layout/running_services_item.xml b/res/layout/running_services_item.xml
new file mode 100644
index 0000000..6748a1a
--- /dev/null
+++ b/res/layout/running_services_item.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2008, 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:orientation="vertical"
+    android:gravity="fill" >
+
+    <ImageView android:id="@+id/separator"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:src="?android:attr/listDivider"/>
+    
+    <LinearLayout
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"
+        android:orientation="horizontal"
+        android:paddingRight="6dip"
+        android:paddingLeft="6dip"
+        android:gravity="center_vertical" >
+    
+        <ImageView android:id="@+id/icon"
+            android:layout_width="@android:dimen/app_icon_size"
+            android:layout_height="@android:dimen/app_icon_size"
+            android:layout_marginLeft="5dip"
+            android:layout_marginRight="11dip"
+            android:layout_gravity="center_vertical"
+            android:scaleType="fitCenter"/>
+    
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content" >
+            <TextView android:id="@+id/name"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:textAppearance="?android:attr/textAppearanceMedium"
+                android:textStyle="bold"
+                android:singleLine="true"
+                android:ellipsize="marquee"
+                android:layout_marginBottom="2dip" />
+            <LinearLayout
+                android:orientation="horizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" >
+                <TextView android:id="@+id/run_time"
+                    android:layout_marginTop="-4dip"
+                    android:layout_gravity="center_vertical|left"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="1"
+                    android:paddingRight="4dip"
+                    android:singleLine="true"
+                    android:ellipsize="marquee"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+                <TextView android:id="@+id/size"
+                    android:layout_marginTop="-4dip"
+                    android:layout_gravity="center_vertical|right"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_weight="0"
+                    android:singleLine="true"
+                    android:textAppearance="?android:attr/textAppearanceSmall" />
+            </LinearLayout>
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e1a9bcd..2af9b18 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1403,6 +1403,25 @@
     <!-- Manage applications, version string displayed in app snippet -->
     <string name="version_text">version <xliff:g id="version_num">%1$s</xliff:g></string>
 
+    <!-- Services settings screen, setting option name for the user to go to the screen to view running services -->
+    <string name="runningservices_settings_title">Running services</string>
+    <!-- Services settings screen, setting option summary for the user to go to the screen to view running services  -->
+    <string name="runningservices_settings_summary">View and control currently running services</string>
+    <!-- Label for a service item when it is restarting -->
+    <string name="service_restarting">Restarting</string>
+    <!-- Running services, body text when there are no services to show -->
+    <string name="no_running_services">No running services</string>
+    <!-- Running services, title of dialog to stop a service -->
+    <string name="confirm_stop_service">Stop service?</string>
+    <!-- Running services, message of dialog to stop a service -->
+    <string name="confirm_stop_service_msg">The service will no longer run until
+            started again by its application.  This may have undesireable
+            consequences.</string>
+    <!-- Running services, button to stop a service -->
+    <string name="confirm_stop_stop">Stop</string>
+    <!-- Running services, button to cancel stopping of a service -->
+    <string name="confirm_stop_cancel">Cancel</string>
+    
     <!-- Language Settings --> <skip />
     <!-- Title of setting on main settings screen.  This item will take the user to the screen to tweak settings realted to locale and text -->
     <string name="language_settings">Language &amp; keyboard</string>
diff --git a/res/xml/application_settings.xml b/res/xml/application_settings.xml
index 8d0a7cb..b5418e8 100644
--- a/res/xml/application_settings.xml
+++ b/res/xml/application_settings.xml
@@ -43,6 +43,14 @@
                 android:targetClass="com.android.settings.ManageApplications" />
     </PreferenceScreen>
 
+    <PreferenceScreen
+            android:title="@string/runningservices_settings_title"
+            android:summary="@string/runningservices_settings_summary">
+        <intent android:action="android.intent.action.MAIN"
+                android:targetPackage="com.android.settings"
+                android:targetClass="com.android.settings.RunningServices" />
+    </PreferenceScreen>
+
     <PreferenceScreen 
             android:title="@string/development_settings_title" 
             android:summary="@string/development_settings_summary">
diff --git a/src/com/android/settings/RunningServices.java b/src/com/android/settings/RunningServices.java
new file mode 100644
index 0000000..3340a4b
--- /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<View, ActiveItem> mActiveItems = new HashMap<View, ActiveItem>();
+    
+    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<ComponentName, ServiceItem> mServices
+                = new HashMap<ComponentName, ServiceItem>();
+        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<HashMap<String, ProcessItem>> mProcesses
+                = new SparseArray<HashMap<String, ProcessItem>>();
+        
+        final ArrayList<BaseItem> mItems = new ArrayList<BaseItem>();
+        
+        int mSequence = 0;
+        
+        boolean update(Context context, ActivityManager am) {
+            final PackageManager pm = context.getPackageManager();
+            
+            mSequence++;
+            
+            boolean changed = false;
+            
+            List<ActivityManager.RunningServiceInfo> services 
+                    = am.getRunningServices(MAX_SERVICES);
+            if (services == null) {
+                return false;
+            }
+            final int NS = services.size();
+            for (int i=0; i<NS; i++) {
+                ActivityManager.RunningServiceInfo si = services.get(i);
+                // We are not interested in non-started services, because
+                // there is nothing the user can do about them.
+                if (!si.started) {
+                    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();
+                    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<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) {
+                        changed = true;
+                        pit.remove();
+                        if (procs.size() == 0) {
+                            mProcesses.remove(mProcesses.keyAt(i));
+                        }
+                        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) {
+                mItems.clear();
+                for (int i=0; i<mProcesses.size(); i++) {
+                    for (ProcessItem pi : mProcesses.valueAt(i).values()) {
+                        pi.mNeedDivider = false;
+                        mItems.add(pi);
+                        boolean needDivider = false;
+                        for (ServiceItem si : pi.mServices.values()) {
+                            si.mNeedDivider = needDivider;
+                            needDivider = true;
+                            mItems.add(si);
+                        }
+                    }
+                }
+            }
+            
+            return changed;
+        }
+    }
+    
+    static class TimeTicker extends TextView {
+        public TimeTicker(Context context, AttributeSet attrs) {
+            super(context, attrs);
+        }
+    }
+    
+    static class ViewHolder {
+        ImageView separator;
+        ImageView icon;
+        TextView name;
+        TextView runTime;
+        TextView size;
+    }
+    
+    class ServiceListAdapter extends BaseAdapter {
+        final State mState;
+        final LayoutInflater mInflater;
+        
+        ServiceListAdapter(State state) {
+            mState = state;
+            mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        }
+
+        public boolean hasStableIds() {
+            return true;
+        }
+        
+        public int getCount() {
+            return mState.mItems.size();
+        }
+
+        public Object getItem(int position) {
+            return mState.mItems.get(position);
+        }
+
+        public long getItemId(int position) {
+            return position;
+        }
+
+        public boolean areAllItemsEnabled() {
+            return false;
+        }
+
+        public boolean isEnabled(int position) {
+            return !mState.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.runTime = (TextView)v.findViewById(R.id.run_time);
+            h.size = (TextView)v.findViewById(R.id.size);
+            v.setTag(h);
+            return v;
+        }
+        
+        public void bindView(View view, int position) {
+            ViewHolder vh = (ViewHolder) view.getTag();
+            BaseItem item = mState.mItems.get(position);
+            vh.name.setText(item.mDisplayLabel);
+            vh.separator.setVisibility(item.mNeedDivider
+                    ? View.VISIBLE : View.INVISIBLE);
+            if (item.mIsProcess) {
+                view.setBackgroundColor(mProcessBgColor);
+                vh.icon.setImageDrawable(item.mPackageInfo.loadIcon(getPackageManager()));
+                vh.runTime.setText(item.mName);
+                vh.size.setText(item.mSizeStr);
+                mActiveItems.remove(view);
+            } else {
+                view.setBackgroundDrawable(null);
+                vh.icon.setImageDrawable(null);
+                vh.runTime.setText("");
+                ActiveItem ai = new ActiveItem();
+                ai.mRootView = view;
+                ai.mItem = item;
+                ai.mHolder = vh;
+                ai.mFirstRunTime = item.mActiveSince;
+                ai.updateTime(RunningServices.this);
+                mActiveItems.put(view, ai);
+            }
+        }
+    }
+    
+    final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_TIMES:
+                    for (ActiveItem ai : mActiveItems.values()) {
+                        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;
+        getListView().setDivider(null);
+        getListView().setAdapter(new ServiceListAdapter(mState));
+    }
+
+    void updateList() {
+        if (mState.update(this, mAm)) {
+            ((ServiceListAdapter)(getListView().getAdapter())).notifyDataSetChanged();
+        }
+    }
+    
+    @Override
+    protected void onListItemClick(ListView l, View v, int position, long id) {
+        BaseItem bi = (BaseItem)l.getAdapter().getItem(position);
+        if (!bi.mIsProcess) {
+            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();
+        }
+    }
+}