Memory tracking is now handled by a service.

Multiple processes may be tracked and viewed simultaneously.

Also, some changes to the graph:
  * show uss and pss together
  * adjust opacity controls

Change-Id: I20eebaa8cc8faf78b46af2a35d71ee538207f02b
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index baf6990..e1ad9f5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -125,6 +125,11 @@
             </intent-filter>
         </activity>
 
+        <service android:name="com.android.launcher3.MemoryTracker"
+            android:enabled="@bool/debug_memory_enabled"
+            >
+        </service>
+
         <!-- Intent received used to prepopulate the default workspace. -->
         <receiver
             android:name="com.android.launcher3.PreloadReceiver"
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 0253103..be01682 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1043,10 +1043,12 @@
 
         if (getResources().getBoolean(R.bool.debug_memory_enabled)) {
             Log.v(TAG, "adding WeightWatcher");
-            ((FrameLayout) mLauncherView).addView(new WeightWatcher(this),
+            final View ww = new WeightWatcher(this);
+            ww.setAlpha(0.5f);
+            ((FrameLayout) mLauncherView).addView(ww,
                     new FrameLayout.LayoutParams(
                             FrameLayout.LayoutParams.MATCH_PARENT,
-                            44,
+                            FrameLayout.LayoutParams.WRAP_CONTENT,
                             Gravity.BOTTOM)
             );
         }
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index af9acb1..aeafcf0 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -17,16 +17,15 @@
 package com.android.launcher3;
 
 import android.app.SearchManager;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
+import android.content.*;
 import android.content.res.Configuration;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Debug;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
 
 import java.io.File;
 import java.io.IOException;
@@ -63,10 +62,21 @@
     private LauncherAppState() { }
 
     private void initialize(Context context) {
+        Log.v(Launcher.TAG, "LauncherAppState initialize() called in process " + android.os.Process.myPid());
+
         mContext = context;
 
         mStarttime = System.currentTimeMillis();
 
+        if (context.getResources().getBoolean(R.bool.debug_memory_enabled)) {
+            context.startService(new Intent(context, MemoryTracker.class)
+                    .setAction(MemoryTracker.ACTION_START_TRACKING)
+                    .putExtra("pid", android.os.Process.myPid())
+                    .putExtra("name", "L")
+                    );
+        }
+
+
         // set sIsScreenXLarge and sScreenDensity *before* creating icon cache
         sIsScreenLarge = context.getResources().getBoolean(R.bool.is_large_screen);
         sScreenDensity = context.getResources().getDisplayMetrics().density;
diff --git a/src/com/android/launcher3/MemoryDumpActivity.java b/src/com/android/launcher3/MemoryDumpActivity.java
index 19b1c4e..51bc308 100644
--- a/src/com/android/launcher3/MemoryDumpActivity.java
+++ b/src/com/android/launcher3/MemoryDumpActivity.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2013 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.launcher3;
 
 import android.app.Activity;
diff --git a/src/com/android/launcher3/MemoryTracker.java b/src/com/android/launcher3/MemoryTracker.java
new file mode 100644
index 0000000..600344e
--- /dev/null
+++ b/src/com/android/launcher3/MemoryTracker.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2013 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.launcher3;
+
+import android.app.ActivityManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.*;
+import android.util.Log;
+import android.util.LongSparseArray;
+
+import java.util.ArrayList;
+
+public class MemoryTracker extends Service {
+    public static final String TAG = MemoryTracker.class.getSimpleName();
+    public static final String ACTION_START_TRACKING = "com.android.launcher3.action.START_TRACKING";
+
+    private static final long UPDATE_RATE = 5000;
+
+    private static final int MSG_START = 1;
+    private static final int MSG_STOP = 2;
+    private static final int MSG_UPDATE = 3;
+
+    public static class ProcessMemInfo {
+        public int pid;
+        public String name;
+        public long currentPss, currentUss;
+        public long[] pss = new long[256];
+        public long[] uss = new long[256];
+            //= new Meminfo[(int) (30 * 60 / (UPDATE_RATE / 1000))]; // 30 minutes
+        public long max = 1;
+        public int head = 0;
+        public ProcessMemInfo(int pid, String name) {
+            this.pid = pid;
+            this.name = name;
+        }
+    };
+    public final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<ProcessMemInfo>();
+    public final ArrayList<Long> mPids = new ArrayList<Long>();
+    private int[] mPidsArray = new int[0];
+
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message m) {
+            switch (m.what) {
+                case MSG_START:
+                    mHandler.removeMessages(MSG_UPDATE);
+                    mHandler.sendEmptyMessage(MSG_UPDATE);
+                    break;
+                case MSG_STOP:
+                    mHandler.removeMessages(MSG_UPDATE);
+                    break;
+                case MSG_UPDATE:
+                    update();
+                    mHandler.removeMessages(MSG_UPDATE);
+                    mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
+                    break;
+            }
+        }
+    };
+
+    ActivityManager mAm;
+
+    public ProcessMemInfo getMemInfo(int pid) {
+        return mData.get(pid);
+    }
+
+    public int[] getTrackedProcesses() {
+        return mPidsArray;
+    }
+
+    public void startTrackingProcess(int pid, String name) {
+        mPids.add(new Long(pid));
+        final int N = mPids.size();
+        mPidsArray = new int[N];
+        StringBuffer sb = new StringBuffer("Now tracking processes: ");
+        for (int i=0; i<N; i++) {
+            final int p = mPids.get(i).intValue();
+            mPidsArray[i] = p;
+            sb.append(p); sb.append(" ");
+        }
+        mData.put(pid, new ProcessMemInfo(pid, name));
+        Log.v(TAG, sb.toString());
+    }
+
+    void update() {
+        Debug.MemoryInfo[] dinfos = mAm.getProcessMemoryInfo(mPidsArray);
+        for (int i=0; i<dinfos.length; i++) {
+            Debug.MemoryInfo dinfo = dinfos[i];
+            final long pid = mPids.get(i).intValue();
+            final ProcessMemInfo info = mData.get(pid);
+            info.head = (info.head+1) % info.pss.length;
+            info.pss[info.head] = info.currentPss = dinfo.getTotalPss();
+            info.uss[info.head] = info.currentUss = dinfo.getTotalPrivateDirty();
+            if (info.currentPss > info.max) info.max = info.currentPss;
+            if (info.currentUss > info.max) info.max = info.currentUss;
+            //Log.v(TAG, "update: pid " + pid + " pss=" + info.currentPss + " uss=" + info.currentUss);
+        }
+
+        // XXX: notify listeners
+    }
+
+    @Override
+    public void onCreate() {
+        mAm = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
+    }
+
+    @Override
+    public void onDestroy() {
+        mHandler.sendEmptyMessage(MSG_STOP);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.i(TAG, "Received start id " + startId + ": " + intent);
+
+        if (ACTION_START_TRACKING.equals(intent.getAction())) {
+            final Uri uri = intent.getData();
+            final int pid = intent.getIntExtra("pid", -1);
+            final String name = intent.getStringExtra("name");
+            startTrackingProcess(pid, name);
+        }
+
+        mHandler.sendEmptyMessage(MSG_START);
+
+        return START_STICKY;
+    }
+
+    public class MemoryTrackerInterface extends Binder {
+        MemoryTracker getService() {
+            return MemoryTracker.this;
+        }
+    }
+
+    private final IBinder mBinder = new MemoryTrackerInterface();
+
+    public IBinder onBind(Intent intent) {
+        mHandler.sendEmptyMessage(MSG_START);
+
+        return mBinder;
+    }
+}
diff --git a/src/com/android/launcher3/WeightWatcher.java b/src/com/android/launcher3/WeightWatcher.java
index 15de93c..91d79a8 100644
--- a/src/com/android/launcher3/WeightWatcher.java
+++ b/src/com/android/launcher3/WeightWatcher.java
@@ -16,33 +16,36 @@
 
 package com.android.launcher3;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.Paint;
-import android.os.Debug;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Message;
 import android.util.AttributeSet;
+import android.util.Log;
+import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.View;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class WeightWatcher extends LinearLayout {
-    private static final long UPDATE_RATE = 5000;
+    private static final int RAM_GRAPH_RSS_COLOR = 0xFF990000;
+    private static final int RAM_GRAPH_PSS_COLOR = 0xFF99CC00;
+    private static final int TEXT_COLOR = 0xFFFFFFFF;
+    private static final int BACKGROUND_COLOR = 0xa0000000;
 
-    private static final int RAM_GRAPH_COLOR = 0x9099CC00;
-    private static final int TEXT_COLOR = 0x90FFFFFF;
-    private static final int BACKGROUND_COLOR = 0x40000000;
+    private static final int UPDATE_RATE = 5000;
 
     private static final int MSG_START = 1;
     private static final int MSG_STOP = 2;
     private static final int MSG_UPDATE = 3;
 
-    TextView mRamText;
-    GraphView mRamGraph;
-    TextView mUptimeText;
-
     Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message m) {
@@ -54,46 +57,45 @@
                     mHandler.removeMessages(MSG_UPDATE);
                     break;
                 case MSG_UPDATE:
-                    update();
+                    final int N = getChildCount();
+                    for (int i=0; i<N; i++) {
+                        ((ProcessWatcher) getChildAt(i)).update();
+                    }
                     mHandler.sendEmptyMessageDelayed(MSG_UPDATE, UPDATE_RATE);
                     break;
             }
         }
     };
+    private MemoryTracker mMemoryService;
 
     public WeightWatcher(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        final float dp = getResources().getDisplayMetrics().density;
+        ServiceConnection connection = new ServiceConnection() {
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                mMemoryService = ((MemoryTracker.MemoryTrackerInterface)service).getService();
+                initViews();
+            }
+
+            public void onServiceDisconnected(ComponentName className) {
+                mMemoryService = null;
+            }
+        };
+        context.bindService(new Intent(context, MemoryTracker.class),
+                connection, Context.BIND_AUTO_CREATE);
+
+        setOrientation(LinearLayout.VERTICAL);
 
         setBackgroundColor(BACKGROUND_COLOR);
+    }
 
-        mRamText = new TextView(getContext());
-        mUptimeText = new TextView(getContext());
-        mRamText.setTextColor(TEXT_COLOR);
-        mUptimeText.setTextColor(TEXT_COLOR);
-
-        final int p = (int)(4*dp);
-        setPadding(p, 0, p, 0);
-
-        mRamGraph = new GraphView(getContext());
-
-        LinearLayout.LayoutParams wrapParams = new LinearLayout.LayoutParams(
-                LinearLayout.LayoutParams.WRAP_CONTENT,
-                LinearLayout.LayoutParams.WRAP_CONTENT
-        );
-        wrapParams.gravity = Gravity.LEFT | Gravity.CENTER_VERTICAL;
-        wrapParams.setMarginEnd((int)(8*dp));
-
-        LinearLayout.LayoutParams fillParams = new LinearLayout.LayoutParams(
-                0,
-                LinearLayout.LayoutParams.MATCH_PARENT,
-                1.0f
-        );
-
-        addView(mUptimeText, wrapParams);
-        addView(mRamText, wrapParams);
-        addView(mRamGraph, fillParams);
+    public void initViews() {
+        int[] processes = mMemoryService.getTrackedProcesses();
+        for (int i=0; i<processes.length; i++) {
+            final ProcessWatcher v = new ProcessWatcher(getContext());
+            v.setPid(processes[i]);
+            addView(v);
+        }
     }
 
     public WeightWatcher(Context context) {
@@ -107,12 +109,6 @@
     }
 
     @Override
-    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
-        mRamText.setTextSize(h * 0.25f);
-        mUptimeText.setTextSize(h * 0.25f);
-    }
-
-    @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
         mHandler.sendEmptyMessage(MSG_STOP);
@@ -147,51 +143,98 @@
         return sb.toString();
     }
 
-    void update() {
-        final long pss = Debug.getPss();
+    public class ProcessWatcher extends LinearLayout {
+        GraphView mRamGraph;
+        TextView mText;
+        int mPid;
+        private MemoryTracker.ProcessMemInfo mMemInfo;
 
-        mRamGraph.add(pss);
-        mRamText.setText("pss=" + pss);
-        mUptimeText.setText("uptime=" + getUptimeString());
-
-        postInvalidate();
-    }
-
-    public static class GraphView extends View {
-        final long[] data = new long[256];
-        long max = 1;
-        int head = 0;
-
-        Paint paint;
-
-        public GraphView(Context context, AttributeSet attrs) {
-            super(context, attrs);
-
-            paint = new Paint();
-            paint.setColor(RAM_GRAPH_COLOR);
-        }
-
-        public GraphView(Context context) {
+        public ProcessWatcher(Context context) {
             this(context, null);
         }
 
-        public void add(long dat) {
-            head = (head+1) % data.length;
-            data[head] = dat;
-            if (dat > max) max = dat;
-            invalidate();
+        public ProcessWatcher(Context context, AttributeSet attrs) {
+            super(context, attrs);
+
+            final float dp = getResources().getDisplayMetrics().density;
+
+            mText = new TextView(getContext());
+            mText.setTextColor(TEXT_COLOR);
+            mText.setTextSize(TypedValue.COMPLEX_UNIT_PX, 10 * dp);
+            mText.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+
+            final int p = (int)(2*dp);
+            setPadding(p, 0, p, 0);
+
+            mRamGraph = new GraphView(getContext());
+
+            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
+                    0,
+                    (int)(22 * dp),
+                    1f
+            );
+
+            addView(mText, params);
+            params.leftMargin = (int)(4*dp);
+            params.weight = 0f;
+            params.width = (int)(200 * dp);
+            addView(mRamGraph, params);
         }
 
-        @Override
-        public void onDraw(Canvas c) {
-            int w = c.getWidth();
-            int h = c.getHeight();
+        public void setPid(int pid) {
+            mPid = pid;
+            mMemInfo = mMemoryService.getMemInfo(mPid);
+        }
 
-            final float barWidth = (float) w / data.length;
-            final float scale = (float) h / max;
+        public void update() {
+            //Log.v("WeightWatcher.ProcessWatcher",
+            //        "MSG_UPDATE pss=" + mMemInfo.currentPss);
+            mText.setText("(" + mMemInfo.name + "/" + mPid + ") up " + getUptimeString()
+                          + " P=" + mMemInfo.currentPss
+                          + " U=" + mMemInfo.currentUss
+                          );
+            mRamGraph.invalidate();
+        }
 
-            for (int i=0; i<data.length; i++) {
-                c.drawRect(i * barWidth, h - scale * data[i], (i+1) * barWidth, h, paint);
+        public class GraphView extends View {
+            Paint pssPaint, ussPaint, headPaint;
+
+            public GraphView(Context context, AttributeSet attrs) {
+                super(context, attrs);
+
+                pssPaint = new Paint();
+                pssPaint.setColor(RAM_GRAPH_PSS_COLOR);
+                ussPaint = new Paint();
+                ussPaint.setColor(RAM_GRAPH_RSS_COLOR);
+                headPaint = new Paint();
+                headPaint.setColor(Color.WHITE);
+            }
+
+            public GraphView(Context context) {
+                this(context, null);
+            }
+
+            @Override
+            public void onDraw(Canvas c) {
+                int w = c.getWidth();
+                int h = c.getHeight();
+
+                if (mMemInfo == null) return;
+
+                final int N = mMemInfo.pss.length;
+                final float barStep = (float) w / N;
+                final float barWidth = Math.max(1, barStep);
+                final float scale = (float) h / mMemInfo.max;
+
+                int i;
+                float x;
+                for (i=0; i<N; i++) {
+                    x = i * barStep;
+                    c.drawRect(x, h - scale * mMemInfo.pss[i], x + barWidth, h, pssPaint);
+                    c.drawRect(x, h - scale * mMemInfo.uss[i], x + barWidth, h, ussPaint);
+                }
+                x = mMemInfo.head * barStep;
+                c.drawRect(x, 0, x + barWidth, h, headPaint);
             }
         }
     }