Add new debug feature to automatically create heap dumps.

Not yet working, unless you turn off SELinux enforcing.
We need to update SElinux to allow the system process
to give apps access to /data/system/heapdump/javaheap.bin.

Currently watching can only be enabled through the shell,
such as:

adb shell am set-watch-heap com.android.systemui 1024

The last number is the process pss size in bytes, so this is
asking us to warn if it goes about 1K which will be all the
time.

Change-Id: I2089e5db2927afca0bf01a363c6247ee5dcb26e8
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 3197461..997f69d 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2423,6 +2423,23 @@
             reply.writeNoException();
             return true;
         }
+
+        case SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            String procName = data.readString();
+            long maxMemSize = data.readLong();
+            setDumpHeapDebugLimit(procName, maxMemSize);
+            reply.writeNoException();
+            return true;
+        }
+
+        case DUMP_HEAP_FINISHED_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            String path = data.readString();
+            dumpHeapFinished(path);
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -5616,5 +5633,30 @@
         reply.recycle();
     }
 
+    @Override
+    public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(processName);
+        data.writeLong(maxMemSize);
+        mRemote.transact(SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    @Override
+    public void dumpHeapFinished(String path) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(path);
+        mRemote.transact(DUMP_HEAP_FINISHED_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 653b951..c93cf22 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4247,6 +4247,10 @@
         } else {
             Debug.dumpNativeHeap(dhd.fd.getFileDescriptor());
         }
+        try {
+            ActivityManagerNative.getDefault().dumpHeapFinished(dhd.path);
+        } catch (RemoteException e) {
+        }
     }
 
     final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 1277cfa..3dcbdd2 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -482,6 +482,9 @@
     public void systemBackupRestored() throws RemoteException;
     public void notifyCleartextNetwork(int uid, byte[] firstPacket) throws RemoteException;
 
+    public void setDumpHeapDebugLimit(String processName, long maxMemSize) throws RemoteException;
+    public void dumpHeapFinished(String path) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -512,7 +515,7 @@
                 dest.writeStrongBinder(null);
             }
             dest.writeStrongBinder(connection);
-            dest.writeInt(noReleaseNeeded ? 1:0);
+            dest.writeInt(noReleaseNeeded ? 1 : 0);
         }
 
         public static final Parcelable.Creator<ContentProviderHolder> CREATOR
@@ -531,7 +534,7 @@
         private ContentProviderHolder(Parcel source) {
             info = ProviderInfo.CREATOR.createFromParcel(source);
             provider = ContentProviderNative.asInterface(
-                source.readStrongBinder());
+                    source.readStrongBinder());
             connection = source.readStrongBinder();
             noReleaseNeeded = source.readInt() != 0;
         }
@@ -813,4 +816,6 @@
     int REQUEST_ASSIST_CONTEXT_EXTRAS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+284;
     int RESIZE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+285;
     int GET_LOCK_TASK_MODE_STATE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+286;
+    int SET_DUMP_HEAP_DEBUG_LIMIT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+287;
+    int DUMP_HEAP_FINISHED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+288;
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 9c00e1c..f0de2b6 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -415,7 +415,6 @@
      * Bit to be bitwise-ored into the {@link #flags} field that should be
      * set if the notification should be canceled when it is clicked by the
      * user.
-
      */
     public static final int FLAG_AUTO_CANCEL        = 0x00000010;
 
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index f607207..84d9ce8 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -16,6 +16,7 @@
 
 package android.util;
 
+import java.io.PrintWriter;
 import java.lang.reflect.Method;
 import java.lang.reflect.InvocationTargetException;
 import java.util.Locale;
@@ -123,4 +124,83 @@
         }
     }
 
+    /** @hide */
+    public static void printSizeValue(PrintWriter pw, long number) {
+        float result = number;
+        String suffix = "";
+        if (result > 900) {
+            suffix = "KB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "MB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "GB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "TB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "PB";
+            result = result / 1024;
+        }
+        String value;
+        if (result < 1) {
+            value = String.format("%.2f", result);
+        } else if (result < 10) {
+            value = String.format("%.1f", result);
+        } else if (result < 100) {
+            value = String.format("%.0f", result);
+        } else {
+            value = String.format("%.0f", result);
+        }
+        pw.print(value);
+        pw.print(suffix);
+    }
+
+    /** @hide */
+    public static String sizeValueToString(long number, StringBuilder outBuilder) {
+        if (outBuilder == null) {
+            outBuilder = new StringBuilder(32);
+        }
+        float result = number;
+        String suffix = "";
+        if (result > 900) {
+            suffix = "KB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "MB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "GB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "TB";
+            result = result / 1024;
+        }
+        if (result > 900) {
+            suffix = "PB";
+            result = result / 1024;
+        }
+        String value;
+        if (result < 1) {
+            value = String.format("%.2f", result);
+        } else if (result < 10) {
+            value = String.format("%.1f", result);
+        } else if (result < 100) {
+            value = String.format("%.0f", result);
+        } else {
+            value = String.format("%.0f", result);
+        }
+        outBuilder.append(value);
+        outBuilder.append(suffix);
+        return outBuilder.toString();
+    }
 }
diff --git a/core/java/com/android/internal/app/DumpHeapActivity.java b/core/java/com/android/internal/app/DumpHeapActivity.java
new file mode 100644
index 0000000..7e70b0c
--- /dev/null
+++ b/core/java/com/android/internal/app/DumpHeapActivity.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 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.internal.app;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ClipData;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.DebugUtils;
+
+/**
+ * This activity is displayed when the system has collected a heap dump from
+ * a large process and the user has selected to share it.
+ */
+public class DumpHeapActivity extends Activity {
+    /** The process we are reporting */
+    public static final String KEY_PROCESS = "process";
+    /** The size limit the process reached */
+    public static final String KEY_SIZE = "size";
+
+    // Broadcast action to determine when to delete the current dump heap data.
+    public static final String ACTION_DELETE_DUMPHEAP = "com.android.server.am.DELETE_DUMPHEAP";
+
+    // Extra for above: delay delete of data, since the user is in the process of sharing it.
+    public static final String EXTRA_DELAY_DELETE = "delay_delete";
+
+    static final public Uri JAVA_URI = Uri.parse("content://com.android.server.heapdump/java");
+
+    String mProcess;
+    long mSize;
+    AlertDialog mDialog;
+    boolean mHandled = false;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mProcess = getIntent().getStringExtra(KEY_PROCESS);
+        mSize = getIntent().getLongExtra(KEY_SIZE, 0);
+        AlertDialog.Builder b = new AlertDialog.Builder(this,
+                android.R.style.Theme_Material_Light_Dialog_Alert);
+        b.setTitle(com.android.internal.R.string.dump_heap_title);
+        b.setMessage(getString(com.android.internal.R.string.dump_heap_text,
+                mProcess, DebugUtils.sizeValueToString(mSize, null)));
+        b.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                mHandled = true;
+                sendBroadcast(new Intent(ACTION_DELETE_DUMPHEAP));
+                finish();
+            }
+        });
+        b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                mHandled = true;
+                Intent broadcast = new Intent(ACTION_DELETE_DUMPHEAP);
+                broadcast.putExtra(EXTRA_DELAY_DELETE, true);
+                sendBroadcast(broadcast);
+                Intent intent = new Intent(Intent.ACTION_SEND);
+                ClipData clip = ClipData.newUri(getContentResolver(), "Heap Dump", JAVA_URI);
+                intent.setClipData(clip);
+                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+                intent.setType(clip.getDescription().getMimeType(0));
+                intent.putExtra(Intent.EXTRA_STREAM, JAVA_URI);
+                startActivity(Intent.createChooser(intent,
+                        getText(com.android.internal.R.string.dump_heap_title)));
+                finish();
+        }
+        });
+        mDialog = b.show();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        if (!isChangingConfigurations()) {
+            if (!mHandled) {
+                sendBroadcast(new Intent(ACTION_DELETE_DUMPHEAP));
+            }
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mDialog.dismiss();
+    }
+}
diff --git a/core/java/com/android/internal/app/ProcessStats.java b/core/java/com/android/internal/app/ProcessStats.java
index 70fb510..75beee9 100644
--- a/core/java/com/android/internal/app/ProcessStats.java
+++ b/core/java/com/android/internal/app/ProcessStats.java
@@ -24,6 +24,7 @@
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.DebugUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -897,17 +898,17 @@
                         pw.print(STATE_NAMES[procStates[ip]]); pw.print(": ");
                         pw.print(count);
                         pw.print(" samples ");
-                        printSizeValue(pw, proc.getPssMinimum(bucket) * 1024);
+                        DebugUtils.printSizeValue(pw, proc.getPssMinimum(bucket) * 1024);
                         pw.print(" ");
-                        printSizeValue(pw, proc.getPssAverage(bucket) * 1024);
+                        DebugUtils.printSizeValue(pw, proc.getPssAverage(bucket) * 1024);
                         pw.print(" ");
-                        printSizeValue(pw, proc.getPssMaximum(bucket) * 1024);
+                        DebugUtils.printSizeValue(pw, proc.getPssMaximum(bucket) * 1024);
                         pw.print(" / ");
-                        printSizeValue(pw, proc.getPssUssMinimum(bucket) * 1024);
+                        DebugUtils.printSizeValue(pw, proc.getPssUssMinimum(bucket) * 1024);
                         pw.print(" ");
-                        printSizeValue(pw, proc.getPssUssAverage(bucket) * 1024);
+                        DebugUtils.printSizeValue(pw, proc.getPssUssAverage(bucket) * 1024);
                         pw.print(" ");
-                        printSizeValue(pw, proc.getPssUssMaximum(bucket) * 1024);
+                        DebugUtils.printSizeValue(pw, proc.getPssUssMaximum(bucket) * 1024);
                         pw.println();
                     }
                 }
@@ -924,9 +925,9 @@
         if (proc.mNumCachedKill != 0) {
             pw.print(prefix); pw.print("Killed from cached state: ");
                     pw.print(proc.mNumCachedKill); pw.print(" times from pss ");
-                    printSizeValue(pw, proc.mMinCachedKillPss * 1024); pw.print("-");
-                    printSizeValue(pw, proc.mAvgCachedKillPss * 1024); pw.print("-");
-                    printSizeValue(pw, proc.mMaxCachedKillPss * 1024); pw.println();
+                    DebugUtils.printSizeValue(pw, proc.mMinCachedKillPss * 1024); pw.print("-");
+                    DebugUtils.printSizeValue(pw, proc.mAvgCachedKillPss * 1024); pw.print("-");
+                    DebugUtils.printSizeValue(pw, proc.mMaxCachedKillPss * 1024); pw.println();
         }
     }
 
@@ -939,11 +940,11 @@
             int bucket, int index) {
         pw.print(prefix); pw.print(label);
         pw.print(": ");
-        printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024);
+        DebugUtils.printSizeValue(pw, getSysMemUsageValue(bucket, index) * 1024);
         pw.print(" min, ");
-        printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024);
+        DebugUtils.printSizeValue(pw, getSysMemUsageValue(bucket, index + 1) * 1024);
         pw.print(" avg, ");
-        printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024);
+        DebugUtils.printSizeValue(pw, getSysMemUsageValue(bucket, index+2) * 1024);
         pw.println(" max");
     }
 
@@ -1150,43 +1151,6 @@
         pw.print("%");
     }
 
-    static void printSizeValue(PrintWriter pw, long number) {
-        float result = number;
-        String suffix = "";
-        if (result > 900) {
-            suffix = "KB";
-            result = result / 1024;
-        }
-        if (result > 900) {
-            suffix = "MB";
-            result = result / 1024;
-        }
-        if (result > 900) {
-            suffix = "GB";
-            result = result / 1024;
-        }
-        if (result > 900) {
-            suffix = "TB";
-            result = result / 1024;
-        }
-        if (result > 900) {
-            suffix = "PB";
-            result = result / 1024;
-        }
-        String value;
-        if (result < 1) {
-            value = String.format("%.2f", result);
-        } else if (result < 10) {
-            value = String.format("%.1f", result);
-        } else if (result < 100) {
-            value = String.format("%.0f", result);
-        } else {
-            value = String.format("%.0f", result);
-        }
-        pw.print(value);
-        pw.print(suffix);
-    }
-
     public static void dumpProcessListCsv(PrintWriter pw, ArrayList<ProcessState> procs,
             boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates,
             boolean sepProcStates, int[] procStates, long now) {
@@ -2437,7 +2401,7 @@
             pw.print(prefix);
             pw.print(label);
             pw.print(": ");
-            printSizeValue(pw, mem);
+            DebugUtils.printSizeValue(pw, mem);
             pw.print(" (");
             pw.print(samples);
             pw.print(" samples)");
@@ -2475,7 +2439,7 @@
         totalPss = printMemoryCategory(pw, "  ", "Z-Ram  ", totalMem.sysMemZRamWeight,
                 totalMem.totalTime, totalPss, totalMem.sysMemSamples);
         pw.print("  TOTAL  : ");
-        printSizeValue(pw, totalPss);
+        DebugUtils.printSizeValue(pw, totalPss);
         pw.println();
         printMemoryCategory(pw, "  ", STATE_NAMES[STATE_SERVICE_RESTARTING],
                 totalMem.processStateWeight[STATE_SERVICE_RESTARTING], totalMem.totalTime, totalPss,
@@ -3781,17 +3745,17 @@
             printPercent(pw, (double) totalTime / (double) overallTime);
             if (numPss > 0) {
                 pw.print(" (");
-                printSizeValue(pw, minPss * 1024);
+                DebugUtils.printSizeValue(pw, minPss * 1024);
                 pw.print("-");
-                printSizeValue(pw, avgPss * 1024);
+                DebugUtils.printSizeValue(pw, avgPss * 1024);
                 pw.print("-");
-                printSizeValue(pw, maxPss * 1024);
+                DebugUtils.printSizeValue(pw, maxPss * 1024);
                 pw.print("/");
-                printSizeValue(pw, minUss * 1024);
+                DebugUtils.printSizeValue(pw, minUss * 1024);
                 pw.print("-");
-                printSizeValue(pw, avgUss * 1024);
+                DebugUtils.printSizeValue(pw, avgUss * 1024);
                 pw.print("-");
-                printSizeValue(pw, maxUss * 1024);
+                DebugUtils.printSizeValue(pw, maxUss * 1024);
                 if (full) {
                     pw.print(" over ");
                     pw.print(numPss);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2ce5bb3..ed4776b 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3033,6 +3033,19 @@
                 android:excludeFromRecents="true"
                 android:process=":ui">
         </activity>
+        <activity android:name="com.android.internal.app.DumpHeapActivity"
+                android:theme="@style/Theme.Translucent.NoTitleBar"
+                android:label="@string/dump_heap_title"
+                android:finishOnCloseSystemDialogs="true"
+                android:noHistory="true"
+                android:excludeFromRecents="true"
+                android:process=":ui">
+        </activity>
+        <provider android:name="com.android.server.am.DumpHeapProvider"
+                android:authorities="com.android.server.heapdump"
+                android:grantUriPermissions="true"
+                android:multiprocess="false"
+                android:singleUser="true" />
 
         <activity android:name="android.accounts.ChooseAccountActivity"
                 android:excludeFromRecents="true"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 67ce159..bf370f4 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3715,6 +3715,23 @@
     <string name="new_app_action">Start <xliff:g id="old_app">%1$s</xliff:g></string>
     <string name="new_app_description">Stop the old app without saving.</string>
 
+    <!-- Notification text to tell the user that a process has exceeded its memory limit. -->
+    <string name="dump_heap_notification"><xliff:g id="proc">%1$s</xliff:g> exceeded memory
+        limit</string>
+
+    <!-- Notification details to tell the user that a process has exceeded its memory limit. -->
+    <string name="dump_heap_notification_detail">Heap dump has been collected;
+        touch to share</string>
+
+    <!-- Title of dialog prompting the user to share a heap dump. -->
+    <string name="dump_heap_title">Share heap dump?</string>
+
+    <!-- Text of dialog prompting the user to share a heap dump. -->
+    <string name="dump_heap_text">The process <xliff:g id="proc">%1$s</xliff:g> has exceeded
+        its process memory limit of <xliff:g id="size">%2$s</xliff:g>.  A heap dump is available
+        for you to share with its developer.  Be careful: this heap dump can contain any
+        of your personal information that the application has access to.</string>
+
     <!-- Displayed in the title of the chooser for things to do with text that
          is to be sent to another application. For example, I can send
          text through SMS or IM.  A dialog with those choices would be shown,
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 078c12f..b204a0b 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1723,6 +1723,10 @@
   <java-symbol type="string" name="data_usage_wifi_limit_title" />
   <java-symbol type="string" name="default_wallpaper_component" />
   <java-symbol type="string" name="dlg_ok" />
+  <java-symbol type="string" name="dump_heap_notification" />
+  <java-symbol type="string" name="dump_heap_notification_detail" />
+  <java-symbol type="string" name="dump_heap_text" />
+  <java-symbol type="string" name="dump_heap_title" />
   <java-symbol type="string" name="factorytest_failed" />
   <java-symbol type="string" name="factorytest_no_action" />
   <java-symbol type="string" name="factorytest_not_system" />