Include all processes in hprof dumps.

The emailed file is now a zipfile containing one .ahprof
file for each process known to MemoryTracker.

Change-Id: If4a73df9afd38756cc01ff37b2d249346e5f7e9f
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index e1ad9f5..09a94ae 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -116,7 +116,8 @@
             android:theme="@android:style/Theme.NoDisplay"
             android:label="@string/debug_memory_activity"
             android:enabled="@bool/debug_memory_enabled"
-            android:icon="@null"
+            android:excludeFromRecents="true"
+            android:icon="@mipmap/ic_launcher_home"
             >
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/src/com/android/launcher3/MemoryDumpActivity.java b/src/com/android/launcher3/MemoryDumpActivity.java
index 51bc308..b437c22 100644
--- a/src/com/android/launcher3/MemoryDumpActivity.java
+++ b/src/com/android/launcher3/MemoryDumpActivity.java
@@ -17,66 +17,136 @@
 package com.android.launcher3;
 
 import android.app.Activity;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Debug;
-import android.os.Environment;
+import android.os.*;
 import android.util.Log;
 
-import java.io.File;
-import java.io.IOException;
+import java.io.*;
 import java.util.ArrayList;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 public class MemoryDumpActivity extends Activity {
+    private static final String TAG = "MemoryDumpActivity";
+
+    @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
     }
 
-    public static void dumpHprofAndShare(final Context context) {
+    public static String zipUp(ArrayList<String> paths) {
+        final int BUFSIZ = 256 * 1024; // 256K
+        final byte[] buf = new byte[BUFSIZ];
+        final String zipfilePath = String.format("%s/hprof-%d.zip",
+                Environment.getExternalStorageDirectory(),
+                System.currentTimeMillis());
+        ZipOutputStream zos = null;
         try {
+            OutputStream os = new FileOutputStream(zipfilePath);
+            zos = new ZipOutputStream(new BufferedOutputStream(os));
+            for (String filename : paths) {
+                InputStream is = null;
+                try {
+                    is = new BufferedInputStream(new FileInputStream(filename));
+                    ZipEntry entry = new ZipEntry(filename);
+                    zos.putNextEntry(entry);
+                    int len;
+                    while ( 0 < (len = is.read(buf, 0, BUFSIZ)) ) {
+                        zos.write(buf, 0, len);
+                    }
+                    zos.closeEntry();
+                } finally {
+                    is.close();
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "error zipping up profile data", e);
+            return null;
+        } finally {
+            if (zos != null) {
+                try {
+                    zos.close();
+                } catch (IOException e) {
+                    // ugh, whatever
+                }
+            }
+        }
+        return zipfilePath;
+    }
+
+    public static void dumpHprofAndShare(final Context context, MemoryTracker tracker) {
+        final StringBuilder body = new StringBuilder();
+
+        final ArrayList<String> paths = new ArrayList<String>();
+        for (int pid : tracker.getTrackedProcesses()) {
             final String path = String.format("%s/launcher-memory-%d.ahprof",
                     Environment.getExternalStorageDirectory(),
-                    System.currentTimeMillis());
-            Log.v(Launcher.TAG, "Dumping memory info to " + path);
-
-            android.os.Debug.dumpHprofData(path); // will block
-
-            Intent shareIntent = new Intent(Intent.ACTION_SEND);
-            shareIntent.setType("application/vnd.android.bugreport");
-
-            final long pss = Debug.getPss();
-            final PackageManager pm = context.getPackageManager();
-            shareIntent.putExtra(Intent.EXTRA_SUBJECT, String.format("Launcher memory dump (PSS=%d)", pss));
-            String appVersion;
+                    pid);
+            Log.v(TAG, "Dumping memory info for process " + pid + " to " + path);
+            MemoryTracker.ProcessMemInfo info = tracker.getMemInfo(pid);
+            body.append("pid ").append(pid).append(":")
+                .append(" up=").append(info.getUptime())
+                .append(" pss=").append(info.currentPss)
+                .append(" uss=").append(info.currentUss)
+                .append("\n");
             try {
-                appVersion = pm.getPackageInfo(context.getPackageName(), 0).versionName;
-            } catch (PackageManager.NameNotFoundException e) {
-                appVersion = "?";
+                android.os.Debug.dumpHprofData(path); // will block
+            } catch (IOException e) {
+                Log.e(TAG, "error dumping memory:", e);
             }
-            shareIntent.putExtra(Intent.EXTRA_TEXT, String.format("App version: %s\nBuild: %s",
-                    appVersion, Build.DISPLAY));
-            shareIntent.setType("application/vnd.android.hprof");
 
-            //shareIntent.putExtra(Intent.EXTRA_TEXT, android.os.SystemProperties.get("ro.build.description"));
-
-            final File pathFile = new File(path);
-            final Uri pathUri = Uri.fromFile(pathFile);
-
-            shareIntent.putExtra(Intent.EXTRA_STREAM, pathUri);
-            context.startActivity(shareIntent);
-        } catch (IOException e) {
-            e.printStackTrace();
+            paths.add(path);
         }
+
+        String zipfile = zipUp(paths);
+
+        if (zipfile == null) return;
+
+        Intent shareIntent = new Intent(Intent.ACTION_SEND);
+        shareIntent.setType("application/zip");
+
+        final PackageManager pm = context.getPackageManager();
+        shareIntent.putExtra(Intent.EXTRA_SUBJECT, String.format("Launcher memory dump"));
+        String appVersion;
+        try {
+            appVersion = pm.getPackageInfo(context.getPackageName(), 0).versionName;
+        } catch (PackageManager.NameNotFoundException e) {
+            appVersion = "?";
+        }
+
+        body.append("\nApp version: ").append(appVersion).append("\nBuild: ").append(Build.DISPLAY).append("\n");
+        shareIntent.putExtra(Intent.EXTRA_TEXT, body.toString());
+
+        final File pathFile = new File(zipfile);
+        final Uri pathUri = Uri.fromFile(pathFile);
+
+        shareIntent.putExtra(Intent.EXTRA_STREAM, pathUri);
+        context.startActivity(shareIntent);
     }
 
     @Override
     public void onStart() {
         super.onStart();
-        dumpHprofAndShare(this);
-        finish();
+
+        final ServiceConnection connection = new ServiceConnection() {
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                Log.v(TAG, "service connected, dumping...");
+                dumpHprofAndShare(MemoryDumpActivity.this,
+                        ((MemoryTracker.MemoryTrackerInterface)service).getService());
+                unbindService(this);
+                finish();
+            }
+
+            public void onServiceDisconnected(ComponentName className) {
+            }
+        };
+        Log.v(TAG, "attempting to bind to memory tracker");
+        bindService(new Intent(this, MemoryTracker.class),
+                connection, Context.BIND_AUTO_CREATE);
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/launcher3/MemoryTracker.java b/src/com/android/launcher3/MemoryTracker.java
index c1057a8..395bf9e 100644
--- a/src/com/android/launcher3/MemoryTracker.java
+++ b/src/com/android/launcher3/MemoryTracker.java
@@ -40,6 +40,7 @@
     public static class ProcessMemInfo {
         public int pid;
         public String name;
+        public long startTime;
         public long currentPss, currentUss;
         public long[] pss = new long[256];
         public long[] uss = new long[256];
@@ -49,6 +50,10 @@
         public ProcessMemInfo(int pid, String name) {
             this.pid = pid;
             this.name = name;
+            this.startTime = System.currentTimeMillis();
+        }
+        public long getUptime() {
+            return System.currentTimeMillis() - startTime;
         }
     };
     public final LongSparseArray<ProcessMemInfo> mData = new LongSparseArray<ProcessMemInfo>();
@@ -77,6 +82,14 @@
 
     ActivityManager mAm;
 
+    public static void startTrackingMe(Context context, String name) {
+        context.startService(new Intent(context, MemoryTracker.class)
+                .setAction(MemoryTracker.ACTION_START_TRACKING)
+                .putExtra("pid", android.os.Process.myPid())
+                .putExtra("name", name)
+        );
+    }
+
     public ProcessMemInfo getMemInfo(int pid) {
         return mData.get(pid);
     }
@@ -86,7 +99,11 @@
     }
 
     public void startTrackingProcess(int pid, String name) {
-        mPids.add(new Long(pid));
+        final Long lpid = new Long(pid);
+
+        if (mPids.contains(lpid)) return;
+
+        mPids.add(lpid);
         final int N = mPids.size();
         mPidsArray = new int[N];
         StringBuffer sb = new StringBuffer("Now tracking processes: ");
@@ -110,7 +127,7 @@
             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);
+            Log.v(TAG, "update: pid " + pid + " pss=" + info.currentPss + " uss=" + info.currentUss);
         }
 
         // XXX: notify listeners