DDMS: Add support for profiling with VM able to send the result through JDWP. Do not merge.

Older VMs need to write the trace file on the SD Card which requires
the appropriate permission. This new mode directly streams the trace
file from the VM to DDMS.

Integrated from master to be in SDK Tools r5.

Bug: 2160407
Change-Id: I17d3e314d6325c1bdff041564bc939b2778b563e
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java b/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
index 52c9506..efa47d7 100644
--- a/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
+++ b/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
@@ -241,7 +241,7 @@
     }
 
     public void toggleMethodProfiling() {
-        boolean canStream = false; //mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
+        boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
         try {
             if (mClientData.getMethodProfilingStatus() == MethodProfilingStatus.ON) {
                 if (canStream) {
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java b/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
index 53e1352..a064634 100644
--- a/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
+++ b/ddms/libs/ddmlib/src/com/android/ddmlib/ClientData.java
@@ -18,6 +18,7 @@
 
 import com.android.ddmlib.HeapSegment.HeapSegmentElement;
 
+import java.io.File;
 import java.nio.BufferUnderflowException;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
@@ -315,10 +316,29 @@
         void onSuccess(String remoteFilePath, Client client);
 
         /**
-         * Called when method tracing failed.
+         * Called when a method tracing was successful.
+         * @param remoteFilePath the device-side path of the trace file.
          * @param client the client that was profiled.
          */
-        void onFailure(Client client);
+        void onSuccess(File localFile, Client client);
+
+        /**
+         * Called when method tracing failed to start
+         * @param client the client that was profiled.
+         */
+        void onStartFailure(Client client);
+
+        /**
+         * Called when method tracing failed to end on the VM side
+         * @param client the client that was profiled.
+         */
+        void onEndFailure(Client client);
+
+        /**
+         * Called when method tracing failed to end locally.
+         * @param client the client that was profiled.
+         */
+        void onEndLocalFailure(Client client, String message);
     }
 
     /**
diff --git a/ddms/libs/ddmlib/src/com/android/ddmlib/HandleProfiling.java b/ddms/libs/ddmlib/src/com/android/ddmlib/HandleProfiling.java
index fcc1d26..7d2eb45 100644
--- a/ddms/libs/ddmlib/src/com/android/ddmlib/HandleProfiling.java
+++ b/ddms/libs/ddmlib/src/com/android/ddmlib/HandleProfiling.java
@@ -19,6 +19,8 @@
 import com.android.ddmlib.ClientData.IMethodProfilingHandler;
 import com.android.ddmlib.ClientData.MethodProfilingStatus;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
 
@@ -154,7 +156,7 @@
 
                 Log.d("ddm-prof", "Method profiling has finished");
             } else {
-                handler.onFailure(client);
+                handler.onEndFailure(client);
 
                 Log.w("ddm-prof", "Method profiling has failed (check device log)");
             }
@@ -213,12 +215,42 @@
      * complete .trace file.
      */
     private void handleMPSE(Client client, ByteBuffer data) {
-        // TODO
-        byte[] stuff = new byte[Math.min(100, data.capacity())];
-        data.get(stuff, 0, stuff.length);
-        String sample = new String(stuff);
-        Log.e("ddm-prof", "GOT MPSE (" + data.capacity() + " bytes): '" +
-            sample + "' ...");
+        IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
+        if (handler != null) {
+            FileOutputStream fos = null;
+            try {
+                File f = File.createTempFile(client.getClientData().getClientDescription(),
+                        ".trace");
+                fos = new FileOutputStream(f);
+
+                byte[] stuff = new byte[data.capacity()];
+                data.get(stuff, 0, stuff.length);
+
+                fos.write(stuff);
+                fos.close();
+                fos = null;
+
+                Log.d("ddm-prof", "got trace file, size: " + data.capacity() + " bytes");
+
+                handler.onSuccess(f, client);
+            } catch (IOException e) {
+                handler.onEndLocalFailure(client, e.getMessage());
+
+                Log.e("ddm-prof", "fail to write trace file: " + e.getMessage());
+                Log.e("ddm-prof", e);
+            } finally {
+                if (fos != null) {
+                    try {
+                        fos.close();
+                    } catch (IOException e) {
+                         //ignore
+                    }
+                }
+            }
+        }
+
+        client.getClientData().setMethodProfilingStatus(MethodProfilingStatus.OFF);
+        client.update(Client.CHANGE_METHOD_PROFILING_STATUS);
     }
 
     /**
@@ -255,7 +287,9 @@
     }
 
     private void handleFAIL(Client client, ByteBuffer data) {
-        // this can be sent if MPRS failed (like wrong permission)
+        // this can be sent if
+        // - MPRS failed (like wrong permission)
+        // - MPSE failed for whatever reason
 
         String filename = client.getClientData().getPendingMethodProfiling();
         if (filename != null) {
@@ -265,9 +299,15 @@
             // and notify of failure
             IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
             if (handler != null) {
-                handler.onFailure(client);
+                handler.onStartFailure(client);
             }
-
+        } else {
+            // this is MPRE
+            // notify of failure
+            IMethodProfilingHandler handler = ClientData.getMethodProfilingHandler();
+            if (handler != null) {
+                handler.onEndFailure(client);
+            }
         }
 
         // send a query to know the current status
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/InfoPanel.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/InfoPanel.java
index 6cbb999..d6979cb 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/InfoPanel.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/InfoPanel.java
@@ -164,10 +164,22 @@
             item.setText(1, isDdmAware);
             item = mTable.getItem(ENT_PROCESS_ID);
             item.setText(1, pid);
+
             item = mTable.getItem(ENT_SUPPORTS_PROFILING);
-            item.setText(1, Boolean.toString(cd.hasFeature(ClientData.FEATURE_PROFILING)));
+            if (cd.hasFeature(ClientData.FEATURE_PROFILING_STREAMING)) {
+                item.setText(1, "Yes");
+            } else if (cd.hasFeature(ClientData.FEATURE_PROFILING)) {
+                item.setText(1, "Yes (Application must be able to write on the SD Card)");
+            } else {
+                item.setText(1, "No");
+            }
+
             item = mTable.getItem(ENT_SUPPORTS_HPROF);
-            item.setText(1, Boolean.toString(cd.hasFeature(ClientData.FEATURE_HPROF)));
+            if (cd.hasFeature(ClientData.FEATURE_HPROF)) {
+                item.setText(1, "Yes (Application must be able to write on the SD Card)");
+            } else {
+                item.setText(1, "No");
+            }
         }
 
         mCol2.pack();
diff --git a/ddms/libs/ddmuilib/src/com/android/ddmuilib/handler/MethodProfilingHandler.java b/ddms/libs/ddmuilib/src/com/android/ddmuilib/handler/MethodProfilingHandler.java
index f469a7d..c029186 100644
--- a/ddms/libs/ddmuilib/src/com/android/ddmuilib/handler/MethodProfilingHandler.java
+++ b/ddms/libs/ddmuilib/src/com/android/ddmuilib/handler/MethodProfilingHandler.java
@@ -45,7 +45,7 @@
         super(parentShell);
     }
 
-    public void onFailure(final Client client) {
+    public void onStartFailure(final Client client) {
         mParentShell.getDisplay().asyncExec(new Runnable() {
             public void run() {
                 displayError(
@@ -56,6 +56,28 @@
         });
     }
 
+    public void onEndFailure(final Client client) {
+        mParentShell.getDisplay().asyncExec(new Runnable() {
+            public void run() {
+                displayError(
+                        "Unable to finish Method Profiling for application '%1$s'.\n" +
+                        "Check logcat for more information.",
+                        client.getClientData().getClientDescription());
+            }
+        });
+    }
+
+    public void onEndLocalFailure(final Client client, final String message) {
+        mParentShell.getDisplay().asyncExec(new Runnable() {
+            public void run() {
+                displayError(String.format(
+                        "Unable to write trace file locally for application\n\t%1$s\n\n%2$s",
+                        client.getClientData().getClientDescription(),
+                        message));
+            }
+        });
+    }
+
     public void onSuccess(final String remoteFilePath, final Client client) {
         mParentShell.getDisplay().asyncExec(new Runnable() {
             public void run() {
@@ -85,6 +107,10 @@
         });
     }
 
+    public void onSuccess(File localFile, final Client client) {
+        openInTraceview(localFile.getAbsolutePath());
+    }
+
     private void pullAndOpen(SyncService sync, String remoteFilePath)
             throws InvocationTargetException, InterruptedException, IOException {
         // get a temp file