Add new ViewDebug APIs to profile the event queue.

Change-Id: I225bf288780b0244f459316e2765cfa29cd22c89
diff --git a/api/current.txt b/api/current.txt
index 3503fb3..7c78c06 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -14035,6 +14035,7 @@
     method public void dispatchMessage(android.os.Message);
     method public final void dump(android.util.Printer, java.lang.String);
     method public final android.os.Looper getLooper();
+    method public java.lang.String getMessageName(android.os.Message);
     method public void handleMessage(android.os.Message);
     method public final boolean hasMessages(int);
     method public final boolean hasMessages(int, java.lang.Object);
@@ -14104,13 +14105,13 @@
 
   public class Looper {
     method public void dump(android.util.Printer, java.lang.String);
-    method public static final synchronized android.os.Looper getMainLooper();
+    method public static synchronized android.os.Looper getMainLooper();
     method public java.lang.Thread getThread();
-    method public static final void loop();
-    method public static final android.os.Looper myLooper();
-    method public static final android.os.MessageQueue myQueue();
-    method public static final void prepare();
-    method public static final void prepareMainLooper();
+    method public static void loop();
+    method public static android.os.Looper myLooper();
+    method public static android.os.MessageQueue myQueue();
+    method public static void prepare();
+    method public static void prepareMainLooper();
     method public void quit();
     method public void setMessageLogging(android.util.Printer);
   }
@@ -22642,8 +22643,10 @@
     ctor public ViewDebug();
     method public static void dumpCapturedView(java.lang.String, java.lang.Object);
     method public static void startHierarchyTracing(java.lang.String, android.view.View);
+    method public static void startLooperProfiling(java.io.File);
     method public static void startRecyclerTracing(java.lang.String, android.view.View);
     method public static void stopHierarchyTracing();
+    method public static void stopLooperProfiling();
     method public static void stopRecyclerTracing();
     method public static void trace(android.view.View, android.view.ViewDebug.RecyclerTraceType, int...);
     method public static void trace(android.view.View, android.view.ViewDebug.HierarchyTraceType);
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 165e438..cd39d5c 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -169,6 +169,21 @@
     }
 
     /**
+     * Returns a string representing the name of the specified message.
+     * The default implementation will either return the class name of the
+     * message callback if any, or the hexadecimal representation of the
+     * message "what" field.
+     *  
+     * @param message The message whose name is being queried 
+     */
+    public String getMessageName(Message message) {
+        if (message.callback != null) {
+            return message.callback.getClass().getName();
+        }
+        return "0x" + Integer.toHexString(message.what);
+    }
+
+    /**
      * Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
      * creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
      *  If you don't want that facility, just call Message.obtain() instead.
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index c0be664..720e802b 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -52,7 +52,6 @@
   */
 public class Looper {
     private static final String TAG = "Looper";
-    private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE);
 
     // sThreadLocal.get() will return null unless you've called prepare().
     private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@@ -70,7 +69,7 @@
       * {@link #loop()} after calling this method, and end it by calling
       * {@link #quit()}.
       */
-    public static final void prepare() {
+    public static void prepare() {
         if (sThreadLocal.get() != null) {
             throw new RuntimeException("Only one Looper may be created per thread");
         }
@@ -83,7 +82,7 @@
      * is created by the Android environment, so you should never need
      * to call this function yourself.  See also: {@link #prepare()}
      */
-    public static final void prepareMainLooper() {
+    public static void prepareMainLooper() {
         prepare();
         setMainLooper(myLooper());
         myLooper().mQueue.mQuitAllowed = false;
@@ -95,7 +94,7 @@
 
     /** Returns the application's main looper, which lives in the main thread of the application.
      */
-    public synchronized static final Looper getMainLooper() {
+    public synchronized static Looper getMainLooper() {
         return mMainLooper;
     }
 
@@ -103,7 +102,7 @@
      * Run the message queue in this thread. Be sure to call
      * {@link #quit()} to end the loop.
      */
-    public static final void loop() {
+    public static void loop() {
         Looper me = myLooper();
         if (me == null) {
             throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
@@ -122,20 +121,36 @@
                     // No target is a magic identifier for the quit message.
                     return;
                 }
-                if (me.mLogging != null) me.mLogging.println(
-                        ">>>>> Dispatching to " + msg.target + " "
-                        + msg.callback + ": " + msg.what
-                        );
+
+                long wallStart = 0;
+                long threadStart = 0;
+
+                // This must be in a local variable, in case a UI event sets the logger
+                Printer logging = me.mLogging;
+                if (logging != null) {
+                    logging.println(">>>>> Dispatching to " + msg.target + " " +
+                            msg.callback + ": " + msg.what);
+                    wallStart = System.currentTimeMillis();
+                    threadStart = SystemClock.currentThreadTimeMillis();
+                }
+
                 msg.target.dispatchMessage(msg);
-                if (me.mLogging != null) me.mLogging.println(
-                        "<<<<< Finished to    " + msg.target + " "
-                        + msg.callback);
-                
+
+                if (logging != null) {
+                    long wallTime = System.currentTimeMillis() - wallStart;
+                    long threadTime = SystemClock.currentThreadTimeMillis() - threadStart;
+
+                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
+                    if (logging instanceof Profiler) {
+                        ((Profiler) logging).profile(msg, wallStart, wallTime, threadTime);
+                    }
+                }
+
                 // Make sure that during the course of dispatching the
                 // identity of the thread wasn't corrupted.
                 final long newIdent = Binder.clearCallingIdentity();
                 if (ident != newIdent) {
-                    Log.wtf("Looper", "Thread identity changed from 0x"
+                    Log.wtf(TAG, "Thread identity changed from 0x"
                             + Long.toHexString(ident) + " to 0x"
                             + Long.toHexString(newIdent) + " while dispatching to "
                             + msg.target.getClass().getName() + " "
@@ -151,7 +166,7 @@
      * Return the Looper object associated with the current thread.  Returns
      * null if the calling thread is not associated with a Looper.
      */
-    public static final Looper myLooper() {
+    public static Looper myLooper() {
         return sThreadLocal.get();
     }
 
@@ -173,7 +188,7 @@
      * thread.  This must be called from a thread running a Looper, or a
      * NullPointerException will be thrown.
      */
-    public static final MessageQueue myQueue() {
+    public static MessageQueue myQueue() {
         return myLooper().mQueue;
     }
 
@@ -225,23 +240,13 @@
     }
 
     public String toString() {
-        return "Looper{"
-            + Integer.toHexString(System.identityHashCode(this))
-            + "}";
+        return "Looper{" + Integer.toHexString(System.identityHashCode(this)) + "}";
     }
 
-    static class HandlerException extends Exception {
-
-        HandlerException(Message message, Throwable cause) {
-            super(createMessage(cause), cause);
-        }
-
-        static String createMessage(Throwable cause) {
-            String causeMsg = cause.getMessage();
-            if (causeMsg == null) {
-                causeMsg = cause.toString();
-            }
-            return causeMsg;
-        }
+    /**
+     * @hide
+     */
+    public static interface Profiler {
+        void profile(Message message, long wallStart, long wallTime, long threadTime);
     }
 }
diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java
index d539a03..ac73611 100644
--- a/core/java/android/view/ViewAncestor.java
+++ b/core/java/android/view/ViewAncestor.java
@@ -2209,6 +2209,62 @@
     public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT = 1023;
 
     @Override
+    public String getMessageName(Message message) {
+        switch (message.what) {
+            case DO_TRAVERSAL:
+                return "DO_TRAVERSAL";
+            case DIE:
+                return "DIE";
+            case RESIZED:
+                return "RESIZED";
+            case RESIZED_REPORT:
+                return "RESIZED_REPORT";
+            case WINDOW_FOCUS_CHANGED:
+                return "WINDOW_FOCUS_CHANGED";
+            case DISPATCH_KEY:
+                return "DISPATCH_KEY";
+            case DISPATCH_POINTER:
+                return "DISPATCH_POINTER";
+            case DISPATCH_TRACKBALL:
+                return "DISPATCH_TRACKBALL";
+            case DISPATCH_APP_VISIBILITY:
+                return "DISPATCH_APP_VISIBILITY";
+            case DISPATCH_GET_NEW_SURFACE:
+                return "DISPATCH_GET_NEW_SURFACE";
+            case FINISHED_EVENT:
+                return "FINISHED_EVENT";
+            case DISPATCH_KEY_FROM_IME:
+                return "DISPATCH_KEY_FROM_IME";
+            case FINISH_INPUT_CONNECTION:
+                return "FINISH_INPUT_CONNECTION";
+            case CHECK_FOCUS:
+                return "CHECK_FOCUS";
+            case CLOSE_SYSTEM_DIALOGS:
+                return "CLOSE_SYSTEM_DIALOGS";
+            case DISPATCH_DRAG_EVENT:
+                return "DISPATCH_DRAG_EVENT";
+            case DISPATCH_DRAG_LOCATION_EVENT:
+                return "DISPATCH_DRAG_LOCATION_EVENT";
+            case DISPATCH_SYSTEM_UI_VISIBILITY:
+                return "DISPATCH_SYSTEM_UI_VISIBILITY";
+            case DISPATCH_GENERIC_MOTION:
+                return "DISPATCH_GENERIC_MOTION";
+            case UPDATE_CONFIGURATION:
+                return "UPDATE_CONFIGURATION";
+            case DO_PERFORM_ACCESSIBILITY_ACTION:
+                return "DO_PERFORM_ACCESSIBILITY_ACTION";
+            case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID:
+                return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID";
+            case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID:
+                return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
+            case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT:
+                return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT";
+                                                                                                                                                                                                                                    
+        }
+        return super.getMessageName(message);
+    }
+
+    @Override
     public void handleMessage(Message msg) {
         switch (msg.what) {
         case View.AttachInfo.INVALIDATE_MSG:
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index f014070..f7f5a21 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -16,41 +16,45 @@
 
 package android.view;
 
-import android.util.Log;
-import android.util.DisplayMetrics;
-import android.content.res.Resources;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Rect;
-import android.os.Environment;
 import android.os.Debug;
+import android.os.Environment;
+import android.os.Looper;
+import android.os.Message;
 import android.os.RemoteException;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Printer;
 
-import java.io.ByteArrayOutputStream;
-import java.io.File;
+import java.io.BufferedOutputStream;
 import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
 import java.io.FileWriter;
 import java.io.IOException;
-import java.io.FileOutputStream;
-import java.io.DataOutputStream;
-import java.io.OutputStreamWriter;
-import java.io.BufferedOutputStream;
 import java.io.OutputStream;
-import java.util.List;
-import java.util.LinkedList;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.lang.annotation.Target;
+import java.io.OutputStreamWriter;
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.InvocationTargetException;
+import java.lang.annotation.Target;
 import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Various debugging/tracing tools related to {@link View} and the view hierarchy.
@@ -106,13 +110,6 @@
     public static final boolean DEBUG_PROFILE_LAYOUT = false;
 
     /**
-     * Profiles real fps (times between draws) and displays the result.
-     *
-     * @hide
-     */
-    public static final boolean DEBUG_SHOW_FPS = false;
-
-    /**
      * Enables detailed logging of drag/drop operations.
      * @hide
      */
@@ -396,6 +393,9 @@
     private static List<RecyclerTrace> sRecyclerTraces;
     private static String sRecyclerTracePrefix;
 
+    private static final ThreadLocal<LooperProfiler> sLooperProfilerStorage =
+            new ThreadLocal<LooperProfiler>();
+
     /**
      * Returns the number of instanciated Views.
      *
@@ -419,6 +419,124 @@
     }
 
     /**
+     * Starts profiling the looper associated with the current thread.
+     * You must call {@link #stopLooperProfiling} to end profiling
+     * and obtain the traces. Both methods must be invoked on the
+     * same thread.
+     * 
+     * @param traceFile The path where to write the looper traces
+     * 
+     * @see #stopLooperProfiling() 
+     */
+    public static void startLooperProfiling(File traceFile) {
+        if (sLooperProfilerStorage.get() == null) {
+            LooperProfiler profiler = new LooperProfiler(traceFile);
+            sLooperProfilerStorage.set(profiler);
+            Looper.myLooper().setMessageLogging(profiler);
+        }
+    }
+
+    /**
+     * Stops profiling the looper associated with the current thread.
+     * 
+     * @see #startLooperProfiling(java.io.File) 
+     */
+    public static void stopLooperProfiling() {
+        LooperProfiler profiler = sLooperProfilerStorage.get();
+        if (profiler != null) {
+            sLooperProfilerStorage.remove();
+            Looper.myLooper().setMessageLogging(null);
+            profiler.save();
+        }
+    }
+
+    private static class LooperProfiler implements Looper.Profiler, Printer {
+        private static final int LOOPER_PROFILER_VERSION = 1;
+
+        private static final String LOG_TAG = "LooperProfiler";
+
+        private final ArrayList<Entry> mTraces = new ArrayList<Entry>(512);
+        private final File mTraceFile;
+
+        public LooperProfiler(File traceFile) {
+            mTraceFile = traceFile;
+        }
+
+        @Override
+        public void println(String x) {
+            // Ignore messages
+        }
+
+        @Override
+        public void profile(Message message, long wallStart, long wallTime, long threadTime) {
+            Entry entry = new Entry();
+            entry.messageId = message.what;
+            entry.name = message.getTarget().getMessageName(message);
+            entry.wallStart = wallStart;
+            entry.wallTime = wallTime;
+            entry.threadTime = threadTime;
+
+            mTraces.add(entry);
+        }
+
+        void save() {
+            // Don't block the UI thread
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    saveTraces();
+                }
+            }, "LooperProfiler[" + mTraceFile + "]").start();
+        }
+
+        private void saveTraces() {
+            FileOutputStream fos;
+            try {
+                fos = new FileOutputStream(mTraceFile);
+            } catch (FileNotFoundException e) {
+                Log.e(LOG_TAG, "Could not open trace file: " + mTraceFile);
+                return;
+            }
+
+            DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fos));
+
+            try {
+                out.writeInt(LOOPER_PROFILER_VERSION);
+                out.writeInt(mTraces.size());
+                for (Entry entry : mTraces) {
+                    saveTrace(entry, out);
+                }
+
+                Log.d(LOG_TAG, "Looper traces ready: " + mTraceFile);
+            } catch (IOException e) {
+                Log.e(LOG_TAG, "Could not write trace file: ", e);
+            } finally {
+                try {
+                    out.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+        }
+
+        private void saveTrace(Entry entry, DataOutputStream out) throws IOException {
+            out.writeInt(entry.messageId);
+            out.writeUTF(entry.name);
+            out.writeLong(entry.wallStart);
+            out.writeLong(entry.wallTime);
+            out.writeLong(entry.threadTime);
+        }
+
+        static class Entry {
+            int messageId;
+            String name;
+            long wallStart;
+            long wallTime;
+            long threadTime;
+        }
+    }
+
+    /**
      * Outputs a trace to the currently opened recycler traces. The trace records the type of
      * recycler action performed on the supplied view as well as a number of parameters.
      *
diff --git a/tests/HwAccelerationTest/res/layout/list_activity.xml b/tests/HwAccelerationTest/res/layout/list_activity.xml
index 6bba370..1a5d3d9 100644
--- a/tests/HwAccelerationTest/res/layout/list_activity.xml
+++ b/tests/HwAccelerationTest/res/layout/list_activity.xml
@@ -30,8 +30,10 @@
             android:layout_height="wrap_content"
             android:layout_marginLeft="10dip"
             android:layout_marginRight="3dip"
+            
+            android:onClick="startProfiling"
 
-            android:text="Add" />
+            android:text="Start" />
         
         <Button
             android:layout_width="0dip"
@@ -39,8 +41,10 @@
             android:layout_height="wrap_content"
             android:layout_marginLeft="3dip"
             android:layout_marginRight="10dip"
+            
+            android:onClick="stopProfiling"
 
-            android:text="Remove" />
+            android:text="Stop" />
         
     </LinearLayout>
     
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java
index 8fd4f6b..1493ab9 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ListActivity.java
@@ -20,15 +20,19 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.os.Bundle;
+import android.os.Environment;
 import android.util.DisplayMetrics;
 import android.view.ContextMenu;
 import android.view.View;
+import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.widget.ArrayAdapter;
 import android.widget.ListAdapter;
 import android.widget.ListView;
 import android.widget.TextView;
 
+import java.io.File;
+
 @SuppressWarnings({"UnusedDeclaration"})
 public class ListActivity extends Activity {
     private static final String[] DATA_LIST = {
@@ -87,6 +91,15 @@
         
         registerForContextMenu(list);
     }
+    
+    public void startProfiling(View v) {
+        ViewDebug.startLooperProfiling(new File(Environment.getExternalStorageDirectory(),
+                "looper.trace"));
+    }
+    
+    public void stopProfiling(View v) {
+        ViewDebug.stopLooperProfiling();
+    }
 
     @Override
     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {