Make manage apps UI multi-user aware.

The details screens still need some work.

Change-Id: I850cc9ba9a8db7787fae629ae3cb6c6772c726f5
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c4900fd..24fc8b0 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -56,6 +56,9 @@
     <uses-permission android:name="android.permission.MANAGE_USB" />
     <uses-permission android:name="android.permission.SET_POINTER_SPEED" />
     <uses-permission android:name="android.permission.SET_KEYBOARD_LAYOUT" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.COPY_PROTECTED_DATA" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
 
     <application android:label="@string/settings_label"
             android:icon="@mipmap/ic_launcher_settings"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 3db7edc..aefbfd6 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2626,6 +2626,8 @@
     <string name="memory">RAM</string>
     <!-- Text to label a process entry with the process name. -->
     <string name="service_process_name"><xliff:g id="process">%1$s</xliff:g></string>
+    <!-- [CHAR LIMIT=NONE] Label of a running process that represents another user -->
+    <string name="running_process_item_user_label">User: <xliff:g id="user_name">%1$s</xliff:g></string>
     <!-- Descriptive text of a running process: singular process, singular service. -->
     <string name="running_processes_item_description_s_s"><xliff:g id="numprocess">%1$d</xliff:g>
         process and <xliff:g id="numservices">%2$d</xliff:g> service</string>
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index 364f96a..f8c23d7 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -102,6 +102,7 @@
             R.id.device_section,
             R.id.sound_settings,
             R.id.display_settings,
+            R.id.application_settings,
             R.id.personal_section,
             R.id.security_settings,
             R.id.account_settings,
diff --git a/src/com/android/settings/applications/RunningProcessesView.java b/src/com/android/settings/applications/RunningProcessesView.java
index 7c3ebb0..67221ae 100644
--- a/src/com/android/settings/applications/RunningProcessesView.java
+++ b/src/com/android/settings/applications/RunningProcessesView.java
@@ -26,6 +26,7 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.preference.PreferenceActivity;
 import android.text.format.DateUtils;
 import android.text.format.Formatter;
@@ -42,6 +43,7 @@
 import android.widget.AbsListView.RecyclerListener;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 
@@ -49,10 +51,12 @@
         implements AdapterView.OnItemClickListener, RecyclerListener,
         RunningState.OnRefreshUiListener {
     
+    final int mMyUserId;
+
     long SECONDARY_SERVER_MEM;
     
     final HashMap<View, ActiveItem> mActiveItems = new HashMap<View, ActiveItem>();
-    
+
     ActivityManager mAm;
     
     RunningState mState;
@@ -167,9 +171,12 @@
                 if (item.mPackageInfo == null && item instanceof RunningState.MergedItem) {
                     // Items for background processes don't normally load
                     // their labels for performance reasons.  Do it now.
-                    ((RunningState.MergedItem)item).mProcess.ensureLabel(pm);
-                    item.mPackageInfo = ((RunningState.MergedItem)item).mProcess.mPackageInfo;
-                    item.mDisplayLabel = ((RunningState.MergedItem)item).mProcess.mDisplayLabel;
+                    RunningState.MergedItem mergedItem = (RunningState.MergedItem)item;
+                    if (mergedItem.mProcess != null) {
+                        ((RunningState.MergedItem)item).mProcess.ensureLabel(pm);
+                        item.mPackageInfo = ((RunningState.MergedItem)item).mProcess.mPackageInfo;
+                        item.mDisplayLabel = ((RunningState.MergedItem)item).mProcess.mDisplayLabel;
+                    }
                 }
                 name.setText(item.mDisplayLabel);
                 ActiveItem ai = new ActiveItem();
@@ -183,9 +190,7 @@
                     description.setText(item.mDescription);
                 }
                 item.mCurSizeStr = null;
-                if (item.mPackageInfo != null) {
-                    icon.setImageDrawable(item.mPackageInfo.loadIcon(pm));
-                }
+                icon.setImageDrawable(item.loadIcon(rootView.getContext(), state));
                 icon.setVisibility(View.VISIBLE);
                 ai.updateTime(rootView.getContext(), builder);
                 return ai;
@@ -203,7 +208,9 @@
         final RunningState mState;
         final LayoutInflater mInflater;
         boolean mShowBackground;
-        ArrayList<RunningState.MergedItem> mItems;
+        ArrayList<RunningState.MergedItem> mOrigItems;
+        final ArrayList<RunningState.MergedItem> mItems
+                = new ArrayList<RunningState.MergedItem>();
         
         ServiceListAdapter(RunningState state) {
             mState = state;
@@ -230,11 +237,17 @@
             ArrayList<RunningState.MergedItem> newItems =
                 mShowBackground ? mState.getCurrentBackgroundItems()
                         : mState.getCurrentMergedItems();
-            if (mItems != newItems) {
-                mItems = newItems;
-            }
-            if (mItems == null) {
-                mItems = new ArrayList<RunningState.MergedItem>();
+            if (mOrigItems != newItems) {
+                mOrigItems = newItems;
+                if (newItems == null) {
+                    mItems.clear();
+                } else {
+                    mItems.clear();
+                    mItems.addAll(newItems);
+                    if (mShowBackground) {
+                        Collections.sort(mItems, mState.mBackgroundComparator);
+                    }
+                }
             }
         }
         
@@ -374,8 +387,11 @@
         if (mOwner != null) {
             // start new fragment to display extended information
             Bundle args = new Bundle();
-            args.putInt(RunningServiceDetails.KEY_UID, mi.mProcess.mUid);
-            args.putString(RunningServiceDetails.KEY_PROCESS, mi.mProcess.mProcessName);
+            if (mi.mProcess != null) {
+                args.putInt(RunningServiceDetails.KEY_UID, mi.mProcess.mUid);
+                args.putString(RunningServiceDetails.KEY_PROCESS, mi.mProcess.mProcessName);
+            }
+            args.putInt(RunningServiceDetails.KEY_USER_ID, mi.mUserId);
             args.putBoolean(RunningServiceDetails.KEY_BACKGROUND, mAdapter.mShowBackground);
     
             PreferenceActivity pa = (PreferenceActivity)mOwner.getActivity();
@@ -390,6 +406,7 @@
 
     public RunningProcessesView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mMyUserId = UserHandle.myUserId();
     }
     
     public void doCreate(Bundle savedInstanceState) {
diff --git a/src/com/android/settings/applications/RunningServiceDetails.java b/src/com/android/settings/applications/RunningServiceDetails.java
index 2087f21..52ed458 100644
--- a/src/com/android/settings/applications/RunningServiceDetails.java
+++ b/src/com/android/settings/applications/RunningServiceDetails.java
@@ -26,6 +26,7 @@
 import android.os.Debug;
 import android.os.Handler;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -39,35 +40,38 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 
 public class RunningServiceDetails extends Fragment
         implements RunningState.OnRefreshUiListener {
     static final String TAG = "RunningServicesDetails";
-    
+
     static final String KEY_UID = "uid";
+    static final String KEY_USER_ID = "user_id";
     static final String KEY_PROCESS = "process";
     static final String KEY_BACKGROUND = "background";
-    
+
     static final int DIALOG_CONFIRM_STOP = 1;
-    
+
     ActivityManager mAm;
     LayoutInflater mInflater;
-    
+
     RunningState mState;
     boolean mHaveData;
-    
+
     int mUid;
+    int mUserId;
     String mProcessName;
     boolean mShowBackground;
-    
+
     RunningState.MergedItem mMergedItem;
-    
+
     View mRootView;
     ViewGroup mAllDetails;
     ViewGroup mSnippet;
     RunningProcessesView.ActiveItem mSnippetActiveItem;
     RunningProcessesView.ViewHolder mSnippetViewHolder;
-    
+
     int mNumServices, mNumProcesses;
     
     TextView mServicesHeader;
@@ -195,8 +199,14 @@
         if (newItems != null) {
             for (int i=0; i<newItems.size(); i++) {
                 RunningState.MergedItem mi = newItems.get(i);
-                if (mi.mProcess.mUid == mUid
-                        && mi.mProcess.mProcessName.equals(mProcessName)) {
+                if (mi.mUserId != mUserId) {
+                    continue;
+                }
+                if (mUid >= 0 && mi.mProcess != null && mi.mProcess.mUid != mUid) {
+                    continue;
+                }
+                if (mProcessName == null || (mi.mProcess != null
+                        && mProcessName.equals(mi.mProcess.mProcessName))) {
                     item = mi;
                     break;
                 }
@@ -209,8 +219,8 @@
         }
         return false;
     }
-    
-    void addServiceDetailsView(RunningState.ServiceItem si, RunningState.MergedItem mi) {
+
+    void addServicesHeader() {
         if (mNumServices == 0) {
             mServicesHeader = (TextView)mInflater.inflate(R.layout.separator_label,
                     mAllDetails, false);
@@ -218,7 +228,30 @@
             mAllDetails.addView(mServicesHeader);
         }
         mNumServices++;
-        
+    }
+
+    void addProcessesHeader() {
+        if (mNumProcesses == 0) {
+            mProcessesHeader = (TextView)mInflater.inflate(R.layout.separator_label,
+                    mAllDetails, false);
+            mProcessesHeader.setText(R.string.runningservicedetails_processes_title);
+            mAllDetails.addView(mProcessesHeader);
+        }
+        mNumProcesses++;
+    }
+
+    void addServiceDetailsView(RunningState.ServiceItem si, RunningState.MergedItem mi,
+            boolean isService, boolean inclDetails) {
+        if (isService) {
+            addServicesHeader();
+        } else if (mi.mUserId != UserHandle.myUserId()) {
+            // This is being called for another user, and is not a service...
+            // That is, it is a background processes, being added for the
+            // details of a user.  In this case we want a header for processes,
+            // since the top subject line is for the user.
+            addProcessesHeader();
+        }
+
         RunningState.BaseItem bi = si != null ? si : mi;
         
         ActiveDetail detail = new ActiveDetail();
@@ -229,68 +262,74 @@
         detail.mServiceItem = si;
         detail.mViewHolder = new RunningProcessesView.ViewHolder(root);
         detail.mActiveItem = detail.mViewHolder.bind(mState, bi, mBuilder);
-        
+
+        if (!inclDetails) {
+            root.findViewById(R.id.service).setVisibility(View.GONE);
+        }
+
         if (si != null && si.mRunningService.clientLabel != 0) {
             detail.mManageIntent = mAm.getRunningServiceControlPanel(
                     si.mRunningService.service);
         }
         
         TextView description = (TextView)root.findViewById(R.id.comp_description);
-        if (si != null && si.mServiceInfo.descriptionRes != 0) {
-            description.setText(getActivity().getPackageManager().getText(
-                    si.mServiceInfo.packageName, si.mServiceInfo.descriptionRes,
-                    si.mServiceInfo.applicationInfo));
+        detail.mStopButton = (Button)root.findViewById(R.id.left_button);
+        detail.mReportButton = (Button)root.findViewById(R.id.right_button);
+
+        if (isService && mi.mUserId != UserHandle.myUserId()) {
+            // For services from other users, we don't show any description or
+            // controls, because the current user can not perform
+            // actions on them.
+            description.setVisibility(View.GONE);
+            root.findViewById(R.id.control_buttons_panel).setVisibility(View.GONE);
         } else {
-            if (mi.mBackground) {
-                description.setText(R.string.background_process_stop_description);
-            } else if (detail.mManageIntent != null) {
-                try {
-                    Resources clientr = getActivity().getPackageManager().getResourcesForApplication(
-                            si.mRunningService.clientPackage);
-                    String label = clientr.getString(si.mRunningService.clientLabel);
-                    description.setText(getActivity().getString(R.string.service_manage_description,
-                            label));
-                } catch (PackageManager.NameNotFoundException e) {
-                }
+            if (si != null && si.mServiceInfo.descriptionRes != 0) {
+                description.setText(getActivity().getPackageManager().getText(
+                        si.mServiceInfo.packageName, si.mServiceInfo.descriptionRes,
+                        si.mServiceInfo.applicationInfo));
             } else {
-                description.setText(getActivity().getText(si != null
-                        ? R.string.service_stop_description
-                        : R.string.heavy_weight_stop_description));
+                if (mi.mBackground) {
+                    description.setText(R.string.background_process_stop_description);
+                } else if (detail.mManageIntent != null) {
+                    try {
+                        Resources clientr = getActivity().getPackageManager().getResourcesForApplication(
+                                si.mRunningService.clientPackage);
+                        String label = clientr.getString(si.mRunningService.clientLabel);
+                        description.setText(getActivity().getString(R.string.service_manage_description,
+                                label));
+                    } catch (PackageManager.NameNotFoundException e) {
+                    }
+                } else {
+                    description.setText(getActivity().getText(si != null
+                            ? R.string.service_stop_description
+                            : R.string.heavy_weight_stop_description));
+                }
+            }
+
+            detail.mStopButton.setOnClickListener(detail);
+            detail.mStopButton.setText(getActivity().getText(detail.mManageIntent != null
+                    ? R.string.service_manage : R.string.service_stop));
+            detail.mReportButton.setOnClickListener(detail);
+            detail.mReportButton.setText(com.android.internal.R.string.report);
+            // check if error reporting is enabled in secure settings
+            int enabled = Settings.Secure.getInt(getActivity().getContentResolver(),
+                    Settings.Secure.SEND_ACTION_APP_ERROR, 0);
+            if (enabled != 0 && si != null) {
+                detail.mInstaller = ApplicationErrorReport.getErrorReportReceiver(
+                        getActivity(), si.mServiceInfo.packageName,
+                        si.mServiceInfo.applicationInfo.flags);
+                detail.mReportButton.setEnabled(detail.mInstaller != null);
+            } else {
+                detail.mReportButton.setEnabled(false);
             }
         }
-        
-        detail.mStopButton = (Button)root.findViewById(R.id.left_button);
-        detail.mStopButton.setOnClickListener(detail);
-        detail.mStopButton.setText(getActivity().getText(detail.mManageIntent != null
-                ? R.string.service_manage : R.string.service_stop));
 
-        detail.mReportButton = (Button)root.findViewById(R.id.right_button);
-        detail.mReportButton.setOnClickListener(detail);
-        detail.mReportButton.setText(com.android.internal.R.string.report);
-        // check if error reporting is enabled in secure settings
-        int enabled = Settings.Secure.getInt(getActivity().getContentResolver(),
-                Settings.Secure.SEND_ACTION_APP_ERROR, 0);
-        if (enabled != 0 && si != null) {
-            detail.mInstaller = ApplicationErrorReport.getErrorReportReceiver(
-                    getActivity(), si.mServiceInfo.packageName,
-                    si.mServiceInfo.applicationInfo.flags);
-            detail.mReportButton.setEnabled(detail.mInstaller != null);
-        } else {
-            detail.mReportButton.setEnabled(false);
-        }
-        
         mActiveDetails.add(detail);
     }
-    
+
     void addProcessDetailsView(RunningState.ProcessItem pi, boolean isMain) {
-        if (mNumProcesses == 0) {
-            mProcessesHeader = (TextView)mInflater.inflate(R.layout.separator_label,
-                    mAllDetails, false);
-            mProcessesHeader.setText(R.string.runningservicedetails_processes_title);
-            mAllDetails.addView(mProcessesHeader);
-        }
-        mNumProcesses++;
-        
+        addProcessesHeader();
+
         ActiveDetail detail = new ActiveDetail();
         View root = mInflater.inflate(R.layout.running_service_details_process,
                 mAllDetails, false);
@@ -300,7 +339,11 @@
         detail.mActiveItem = detail.mViewHolder.bind(mState, pi, mBuilder);
         
         TextView description = (TextView)root.findViewById(R.id.comp_description);
-        if (isMain) {
+        if (pi.mUserId != UserHandle.myUserId()) {
+            // Processes for another user are all shown batched together; there is
+            // no reason to have a description.
+            description.setVisibility(View.GONE);
+        } else if (isMain) {
             description.setText(R.string.main_running_process_description);
         } else {
             int textid = 0;
@@ -342,7 +385,39 @@
         
         mActiveDetails.add(detail);
     }
+
+    void addDetailsViews(RunningState.MergedItem item, boolean inclServices,
+            boolean inclProcesses) {
+        if (item != null) {
+            if (inclServices) {
+                for (int i=0; i<item.mServices.size(); i++) {
+                    addServiceDetailsView(item.mServices.get(i), item, true, true);
+                }
+            }
+
+            if (inclProcesses) {
+                if (item.mServices.size() <= 0) {
+                    // This item does not have any services, so it must be
+                    // another interesting process...  we will put a fake service
+                    // entry for it, to allow the user to "stop" it.
+                    addServiceDetailsView(null, item, false, item.mUserId != UserHandle.myUserId());
+                } else {
+                    // This screen is actually showing services, so also show
+                    // the process details.
+                    for (int i=-1; i<item.mOtherProcesses.size(); i++) {
+                        RunningState.ProcessItem pi = i < 0 ? item.mProcess
+                                : item.mOtherProcesses.get(i);
+                        if (pi != null && pi.mPid <= 0) {
+                            continue;
+                        }
     
+                        addProcessDetailsView(pi, i < 0);
+                    }
+                }
+            }
+        }
+    }
+
     void addDetailViews() {
         for (int i=mActiveDetails.size()-1; i>=0; i--) {
             mAllDetails.removeView(mActiveDetails.get(i).mRootView);
@@ -358,30 +433,25 @@
             mAllDetails.removeView(mProcessesHeader);
             mProcessesHeader = null;
         }
-        
+
         mNumServices = mNumProcesses = 0;
-        
-        if (mMergedItem != null) {
-            for (int i=0; i<mMergedItem.mServices.size(); i++) {
-                addServiceDetailsView(mMergedItem.mServices.get(i), mMergedItem);
+
+        if (mMergedItem.mUser != null) {
+            ArrayList<RunningState.MergedItem> items;
+            if (mShowBackground) {
+                items = new ArrayList<RunningState.MergedItem>(mMergedItem.mChildren);
+                Collections.sort(items, mState.mBackgroundComparator);
+            } else {
+                items = mMergedItem.mChildren;
             }
-            
-            if (mMergedItem.mServices.size() <= 0) {
-                // This item does not have any services, so it must be
-                // another interesting process...  we will put a fake service
-                // entry for it, to allow the user to "stop" it.
-                addServiceDetailsView(null, mMergedItem);
+            for (int i=0; i<items.size(); i++) {
+                addDetailsViews(items.get(i), true, false);
             }
-            
-            for (int i=-1; i<mMergedItem.mOtherProcesses.size(); i++) {
-                RunningState.ProcessItem pi = i < 0 ? mMergedItem.mProcess
-                        : mMergedItem.mOtherProcesses.get(i);
-                if (pi.mPid <= 0) {
-                    continue;
-                }
-                
-                addProcessDetailsView(pi, i < 0);
+            for (int i=0; i<items.size(); i++) {
+                addDetailsViews(items.get(i), false, true);
             }
+        } else {
+            addDetailsViews(mMergedItem, true, true);
         }
     }
     
@@ -423,8 +493,9 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         
-        mUid = getArguments().getInt(KEY_UID, 0);
-        mProcessName = getArguments().getString(KEY_PROCESS);
+        mUid = getArguments().getInt(KEY_UID, -1);
+        mUserId = getArguments().getInt(KEY_USER_ID, 0);
+        mProcessName = getArguments().getString(KEY_PROCESS, null);
         mShowBackground = getArguments().getBoolean(KEY_BACKGROUND, false);
         
         mAm = (ActivityManager)getActivity().getSystemService(Context.ACTIVITY_SERVICE);
diff --git a/src/com/android/settings/applications/RunningState.java b/src/com/android/settings/applications/RunningState.java
index 1b5310d..838e4fe 100644
--- a/src/com/android/settings/applications/RunningState.java
+++ b/src/com/android/settings/applications/RunningState.java
@@ -27,12 +27,16 @@
 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.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;
@@ -49,6 +53,9 @@
  * 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;
 
@@ -65,6 +72,8 @@
     final Context mApplicationContext;
     final ActivityManager mAm;
     final PackageManager mPm;
+    final UserManager mUm;
+    final int mMyUserId;
 
     OnRefreshUiListener mRefreshUiListener;
 
@@ -100,6 +109,17 @@
     // 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;
@@ -114,7 +134,64 @@
     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
@@ -128,6 +205,7 @@
     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;
@@ -211,25 +289,40 @@
         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) {
+
+        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;
         }
     }
 
@@ -240,8 +333,8 @@
         
         MergedItem mMergedItem;
         
-        public ServiceItem() {
-            super(false);
+        public ServiceItem(int userId) {
+            super(false, userId);
         }
     }
 
@@ -271,7 +364,7 @@
         long mActiveSince;
         
         public ProcessItem(Context context, int uid, String processName) {
-            super(true);
+            super(true, UserHandle.getUserId(uid));
             mDescription = context.getResources().getString(
                     R.string.service_process_name, processName);
             mUid = uid;
@@ -332,8 +425,9 @@
             // If still don't have anything to display, just use the
             // service info.
             if (mServices.size() > 0) {
-                mPackageInfo = mServices.values().iterator().next()
+                ApplicationInfo ai = mServices.values().iterator().next()
                         .mServiceInfo.applicationInfo;
+                mPackageInfo = ai;
                 mDisplayLabel = mPackageInfo.loadLabel(pm);
                 mLabel = mDisplayLabel.toString();
                 return;
@@ -358,7 +452,7 @@
             ServiceItem si = mServices.get(service.service);
             if (si == null) {
                 changed = true;
-                si = new ServiceItem();
+                si = new ServiceItem(mUserId);
                 si.mRunningService = service;
                 try {
                     si.mServiceInfo = pm.getServiceInfo(service.service, 0);
@@ -456,55 +550,96 @@
 
     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() {
-            super(false);
+        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) {
-            mPackageInfo = mProcess.mPackageInfo;
-            mDisplayLabel = mProcess.mDisplayLabel;
-            mLabel = mProcess.mLabel;
             mBackground = background;
-            
-            if (!mBackground) {
-                int numProcesses = (mProcess.mPid > 0 ? 1 : 0) + mOtherProcesses.size();
-                int numServices = mServices.size();
-                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;
+
+            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;
                     }
-                    mDescription = context.getResources().getString(resid, numProcesses,
-                            numServices);
+                }
+                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;
+                    }
                 }
             }
-            
-            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) {
-            mSize = mProcess.mSize;
-            for (int i=0; i<mOtherProcesses.size(); i++) {
-                mSize += mOtherProcesses.get(i).mSize;
+            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(
@@ -518,10 +653,26 @@
             }
             return false;
         }
+
+        public Drawable loadIcon(Context context, RunningState state) {
+            if (mUser == null) {
+                return super.loadIcon(context, state);
+            }
+            if (mUser.mIcon != null) {
+                return mUser.mIcon.getConstantState().newDrawable();
+            }
+            return context.getResources().getDrawable(
+                    com.android.internal.R.drawable.ic_menu_cc);
+        }
     }
-    
-    static class ServiceProcessComparator implements Comparator<ProcessItem> {
+
+    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;
@@ -570,6 +721,8 @@
         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();
@@ -646,6 +799,42 @@
         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 && info.iconPath != null) {
+                    try {
+                        userItem.mUser.mIcon = Drawable.createFromPath(info.iconPath);
+                    } catch (Exception e) {
+                        Log.w(TAG, "Failure loading user picture " + info.iconPath, e);
+                    }
+                }
+                String name = info != null ? info.name : null;
+                if (name == null) {
+                    name = Integer.toString(info.id);
+                }
+                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) {
@@ -940,6 +1129,7 @@
             
             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);
@@ -975,7 +1165,7 @@
                 if (!haveAllMerged || mergedItem == null
                         || mergedItem.mServices.size() != pi.mServices.size()) {
                     // Whoops, we need to build a new MergedItem!
-                    mergedItem = new MergedItem();
+                    mergedItem = new MergedItem(pi.mUserId);
                     for (ServiceItem si : pi.mServices.values()) {
                         mergedItem.mServices.add(si);
                         si.mMergedItem = mergedItem;
@@ -988,9 +1178,13 @@
                 }
                 
                 mergedItem.update(context, false);
-                newMergedItems.add(mergedItem);
+                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();
@@ -998,15 +1192,30 @@
                 ProcessItem proc = mInterestingProcesses.get(i);
                 if (proc.mClient == null && proc.mServices.size() <= 0) {
                     if (proc.mMergedItem == null) {
-                        proc.mMergedItem = new MergedItem();
+                        proc.mMergedItem = new MergedItem(proc.mUserId);
                         proc.mMergedItem.mProcess = proc;
                     }
                     proc.mMergedItem.update(context, false);
-                    newMergedItems.add(0, proc.mMergedItem);
+                    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;
@@ -1047,6 +1256,8 @@
         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];
@@ -1066,18 +1277,22 @@
                     backgroundProcessMemory += proc.mSize;
                     MergedItem mergedItem;
                     if (newBackgroundItems != null) {
-                        mergedItem = proc.mMergedItem = new MergedItem();
+                        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++) {
-                                newBackgroundItems.add(mBackgroundItems.get(bgi));
+                                mergedItem = mBackgroundItems.get(bgi);
+                                diffUsers |= mergedItem.mUserId != mMyUserId;
+                                newBackgroundItems.add(mergedItem);
                             }
-                            mergedItem = proc.mMergedItem = new MergedItem();
+                            mergedItem = proc.mMergedItem = new MergedItem(proc.mUserId);
                             proc.mMergedItem.mProcess = proc;
+                            diffUsers |= mergedItem.mUserId != mMyUserId;
                             newBackgroundItems.add(mergedItem);
                         } else {
                             mergedItem = mBackgroundItems.get(bgIndex);
@@ -1099,7 +1314,42 @@
             if (mBackgroundItems.size() > numBackgroundProcesses) {
                 newBackgroundItems = new ArrayList<MergedItem>(numBackgroundProcesses);
                 for (int bgi=0; bgi<numBackgroundProcesses; bgi++) {
-                    newBackgroundItems.add(mBackgroundItems.get(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);
+                    }
                 }
             }
         }
@@ -1117,6 +1367,7 @@
             mServiceProcessMemory = serviceProcessMemory;
             if (newBackgroundItems != null) {
                 mBackgroundItems = newBackgroundItems;
+                mUserBackgroundItems = newUserBackgroundItems;
                 if (mWatchingBackgroundItems) {
                     changed = true;
                 }
@@ -1150,7 +1401,7 @@
 
     ArrayList<MergedItem> getCurrentBackgroundItems() {
         synchronized (mLock) {
-            return mBackgroundItems;
+            return mUserBackgroundItems;
         }
     }
 }