Merge "Improved trust error messaging (1/2)"
diff --git a/Android.mk b/Android.mk
index 6851765..53e892f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -442,6 +442,7 @@
 
 LOCAL_NO_STANDARD_LIBRARIES := true
 LOCAL_JAVA_LIBRARIES := core-oj core-libart conscrypt okhttp core-junit bouncycastle ext
+LOCAL_STATIC_JAVA_LIBRARIES := framework-protos
 
 LOCAL_MODULE := framework
 
diff --git a/api/current.txt b/api/current.txt
index ed04a6f..2d19b9a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3676,9 +3676,6 @@
     method public void startActivity(android.content.Context, android.content.Intent, android.os.Bundle);
   }
 
-  public static abstract class ActivityManager.BugreportMode implements java.lang.annotation.Annotation {
-  }
-
   public static class ActivityManager.MemoryInfo implements android.os.Parcelable {
     ctor public ActivityManager.MemoryInfo();
     method public int describeContents();
@@ -5783,9 +5780,6 @@
     field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
   }
 
-  public static abstract class DeviceAdminReceiver.BugreportFailureCode implements java.lang.annotation.Annotation {
-  }
-
   public class DevicePolicyManager {
     method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
     method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -34412,6 +34406,8 @@
 
   public abstract class UtteranceProgressListener {
     ctor public UtteranceProgressListener();
+    method public void onAudioAvailable(java.lang.String, byte[]);
+    method public void onBeginSynthesis(java.lang.String, int, int, int);
     method public abstract void onDone(java.lang.String);
     method public abstract deprecated void onError(java.lang.String);
     method public void onError(java.lang.String, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 825fe75..733ad28 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3798,9 +3798,6 @@
     method public void startActivity(android.content.Context, android.content.Intent, android.os.Bundle);
   }
 
-  public static abstract class ActivityManager.BugreportMode implements java.lang.annotation.Annotation {
-  }
-
   public static class ActivityManager.MemoryInfo implements android.os.Parcelable {
     ctor public ActivityManager.MemoryInfo();
     method public int describeContents();
@@ -5918,9 +5915,6 @@
     field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
   }
 
-  public static abstract class DeviceAdminReceiver.BugreportFailureCode implements java.lang.annotation.Annotation {
-  }
-
   public class DevicePolicyManager {
     method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
     method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -36662,6 +36656,8 @@
 
   public abstract class UtteranceProgressListener {
     ctor public UtteranceProgressListener();
+    method public void onAudioAvailable(java.lang.String, byte[]);
+    method public void onBeginSynthesis(java.lang.String, int, int, int);
     method public abstract void onDone(java.lang.String);
     method public abstract deprecated void onError(java.lang.String);
     method public void onError(java.lang.String, int);
diff --git a/api/test-current.txt b/api/test-current.txt
index f096d56..e9173741 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3676,9 +3676,6 @@
     method public void startActivity(android.content.Context, android.content.Intent, android.os.Bundle);
   }
 
-  public static abstract class ActivityManager.BugreportMode implements java.lang.annotation.Annotation {
-  }
-
   public static class ActivityManager.MemoryInfo implements android.os.Parcelable {
     ctor public ActivityManager.MemoryInfo();
     method public int describeContents();
@@ -5785,9 +5782,6 @@
     field public static final java.lang.String EXTRA_LOCK_TASK_PACKAGE = "android.app.extra.LOCK_TASK_PACKAGE";
   }
 
-  public static abstract class DeviceAdminReceiver.BugreportFailureCode implements java.lang.annotation.Annotation {
-  }
-
   public class DevicePolicyManager {
     method public void addCrossProfileIntentFilter(android.content.ComponentName, android.content.IntentFilter, int);
     method public boolean addCrossProfileWidgetProvider(android.content.ComponentName, java.lang.String);
@@ -34426,6 +34420,8 @@
 
   public abstract class UtteranceProgressListener {
     ctor public UtteranceProgressListener();
+    method public void onAudioAvailable(java.lang.String, byte[]);
+    method public void onBeginSynthesis(java.lang.String, int, int, int);
     method public abstract void onDone(java.lang.String);
     method public abstract deprecated void onError(java.lang.String);
     method public void onError(java.lang.String, int);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2dc4fb9..6307477 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -85,16 +85,16 @@
     private final Context mContext;
     private final Handler mHandler;
 
+    /**
+     * Defines acceptable types of bugreports.
+     * @hide
+     */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
             BUGREPORT_OPTION_FULL,
             BUGREPORT_OPTION_INTERACTIVE,
             BUGREPORT_OPTION_REMOTE
     })
-    /**
-     * Defines acceptable types of bugreports.
-     * @hide
-     */
     public @interface BugreportMode {}
     /**
      * Takes a bugreport without user interference (and hence causing less
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index cfd5ca8..3c1ecc7 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -283,17 +283,17 @@
     public static final String EXTRA_BUGREPORT_FAILURE_REASON =
             "android.app.extra.BUGREPORT_FAILURE_REASON";
 
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({
-        BUGREPORT_FAILURE_FAILED_COMPLETING,
-        BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE
-    })
     /**
      * An interface representing reason of bugreport failure.
      *
      * @see #EXTRA_BUGREPORT_FAILURE_REASON
      * @hide
      */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        BUGREPORT_FAILURE_FAILED_COMPLETING,
+        BUGREPORT_FAILURE_FILE_NO_LONGER_AVAILABLE
+    })
     public @interface BugreportFailureCode {}
     /** Bugreport completion process failed. */
     public static final int BUGREPORT_FAILURE_FAILED_COMPLETING = 0;
diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java
index dea766b..ca9931a 100644
--- a/core/java/android/speech/tts/FileSynthesisCallback.java
+++ b/core/java/android/speech/tts/FileSynthesisCallback.java
@@ -111,6 +111,7 @@
                        "of AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT or " +
                        "AudioFormat.ENCODING_PCM_FLOAT");
         }
+        mDispatcher.dispatchOnBeginSynthesis(sampleRateInHz, audioFormat, channelCount);
 
         FileChannel fileChannel = null;
         synchronized (mStateLock) {
@@ -176,6 +177,10 @@
             fileChannel = mFileChannel;
         }
 
+        final byte[] bufferCopy = new byte[length];
+        System.arraycopy(buffer, offset, bufferCopy, 0, length);
+        mDispatcher.dispatchOnAudioAvailable(bufferCopy);
+
         try {
             fileChannel.write(ByteBuffer.wrap(buffer,  offset,  length));
             return TextToSpeech.SUCCESS;
diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index d785c3f..4e3acf6 100644
--- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -22,33 +22,65 @@
  */
 oneway interface ITextToSpeechCallback {
     /**
-     * Tells the client that the synthesis has started.
+     * Tells the client that the synthesis has started playing.
      *
-     * @param utteranceId Unique id identifying synthesis request.
+     * @param utteranceId Unique id identifying the synthesis request.
      */
     void onStart(String utteranceId);
 
     /**
-     * Tells the client that the synthesis has finished.
+     * Tells the client that the synthesis has finished playing.
      *
-     * @param utteranceId Unique id identifying synthesis request.
+     * @param utteranceId Unique id identifying the synthesis request.
      */
     void onSuccess(String utteranceId);
 
     /**
      * Tells the client that the synthesis was stopped.
      *
-     * @param utteranceId Unique id identifying synthesis request.
+     * @param utteranceId Unique id identifying the synthesis request.
      */
     void onStop(String utteranceId, boolean isStarted);
 
     /**
      * Tells the client that the synthesis has failed.
      *
-     * @param utteranceId Unique id identifying synthesis request.
+     * @param utteranceId Unique id identifying the synthesis request.
      * @param errorCode One of the values from
      *        {@link android.speech.tts.v2.TextToSpeech}.
      */
     void onError(String utteranceId, int errorCode);
 
+    /**
+     * Tells the client that the TTS engine has started synthesizing the audio for a request.
+     *
+     * <p>
+     * This doesn't mean the synthesis request has already started playing (for example when there
+     * are synthesis requests ahead of it in the queue), but after receiving this callback you can
+     * expect onAudioAvailable to be called.
+     * </p>
+     *
+     * @param utteranceId Unique id identifying the synthesis request.
+     * @param sampleRateInHz Sample rate in HZ of the generated audio.
+     * @param audioFormat The audio format of the generated audio in the {@link #onAudioAvailable}
+     *        call. Should be one of {@link android.media.AudioFormat.ENCODING_PCM_8BIT},
+     *        {@link android.media.AudioFormat.ENCODING_PCM_16BIT} or
+     *        {@link android.media.AudioFormat.ENCODING_PCM_FLOAT}.
+     * @param channelCount The number of channels.
+     */
+    void onBeginSynthesis(String utteranceId, int sampleRateInHz, int audioFormat, int channelCount);
+
+    /**
+     * Tells the client about a chunk of the synthesized audio.
+     *
+     * <p>
+     * Called when a chunk of the synthesized audio is ready. This may be called more than once for
+     * every synthesis request, thereby streaming the audio to the client.
+     * </p>
+     *
+     * @param utteranceId Unique id identifying the synthesis request.
+     * @param audio The raw audio bytes. Its format is specified by the {@link #onStartAudio}
+     * callback.
+     */
+    void onAudioAvailable(String utteranceId, in byte[] audio);
 }
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index dcc0095..778aa86 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -15,6 +15,7 @@
  */
 package android.speech.tts;
 
+import android.annotation.NonNull;
 import android.media.AudioFormat;
 import android.speech.tts.TextToSpeechService.AudioOutputParams;
 import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
@@ -51,9 +52,10 @@
     private final Object mCallerIdentity;
     private final AbstractEventLogger mLogger;
 
-    PlaybackSynthesisCallback(AudioOutputParams audioParams, AudioPlaybackHandler audioTrackHandler,
-            UtteranceProgressDispatcher dispatcher, Object callerIdentity,
-            AbstractEventLogger logger, boolean clientIsUsingV2) {
+    PlaybackSynthesisCallback(@NonNull AudioOutputParams audioParams,
+            @NonNull AudioPlaybackHandler audioTrackHandler,
+            @NonNull UtteranceProgressDispatcher dispatcher, @NonNull Object callerIdentity,
+            @NonNull AbstractEventLogger logger, boolean clientIsUsingV2) {
         super(clientIsUsingV2);
         mAudioParams = audioParams;
         mAudioTrackHandler = audioTrackHandler;
@@ -130,6 +132,7 @@
                        "of AudioFormat.ENCODING_PCM_8BIT, AudioFormat.ENCODING_PCM_16BIT or " +
                        "AudioFormat.ENCODING_PCM_FLOAT");
         }
+        mDispatcher.dispatchOnBeginSynthesis(sampleRateInHz, audioFormat, channelCount);
 
         int channelConfig = BlockingAudioTrack.getChannelConfig(channelCount);
 
@@ -190,6 +193,7 @@
         // Sigh, another copy.
         final byte[] bufferCopy = new byte[length];
         System.arraycopy(buffer, offset, bufferCopy, 0, length);
+        mDispatcher.dispatchOnAudioAvailable(bufferCopy);
 
         // Might block on mItem.this, if there are too many buffers waiting to
         // be consumed.
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 61c33ff..d55c7bd 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -16,6 +16,7 @@
 package android.speech.tts;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.RawRes;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
@@ -665,7 +666,7 @@
     private OnInitListener mInitListener;
     // Written from an unspecified application thread, read from
     // a binder thread.
-    private volatile UtteranceProgressListener mUtteranceProgressListener;
+    @Nullable private volatile UtteranceProgressListener mUtteranceProgressListener;
     private final Object mStartLock = new Object();
 
     private String mRequestedEngine;
@@ -2133,6 +2134,23 @@
                     listener.onStart(utteranceId);
                 }
             }
+
+            @Override
+            public void onBeginSynthesis(String utteranceId, int sampleRateInHz, int audioFormat,
+                                     int channelCount) {
+                UtteranceProgressListener listener = mUtteranceProgressListener;
+                if (listener != null) {
+                    listener.onBeginSynthesis(utteranceId, sampleRateInHz, audioFormat, channelCount);
+                }
+            }
+
+            @Override
+            public void onAudioAvailable(String utteranceId, byte[] audio) {
+                UtteranceProgressListener listener = mUtteranceProgressListener;
+                if (listener != null) {
+                    listener.onAudioAvailable(utteranceId, audio);
+                }
+            }
         };
 
         private class SetupConnectionAsyncTask extends AsyncTask<Void, Void, Integer> {
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 8c355d8..fc075de 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -15,6 +15,7 @@
  */
 package android.speech.tts;
 
+import android.annotation.NonNull;
 import android.app.Service;
 import android.content.Intent;
 import android.media.AudioAttributes;
@@ -111,7 +112,7 @@
     // A thread and it's associated handler for playing back any audio
     // associated with this TTS engine. Will handle all requests except synthesis
     // to file requests, which occur on the synthesis thread.
-    private AudioPlaybackHandler mAudioPlaybackHandler;
+    @NonNull private AudioPlaybackHandler mAudioPlaybackHandler;
     private TtsEngines mEngineHelper;
 
     private CallbackMap mCallbacks;
@@ -649,6 +650,8 @@
         public void dispatchOnSuccess();
         public void dispatchOnStart();
         public void dispatchOnError(int errorCode);
+        public void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount);
+        public void dispatchOnAudioAvailable(byte[] audio);
     }
 
     /** Set of parameters affecting audio output. */
@@ -853,6 +856,22 @@
             }
         }
 
+        @Override
+        public void dispatchOnBeginSynthesis(int sampleRateInHz, int audioFormat, int channelCount) {
+            final String utteranceId = getUtteranceId();
+            if (utteranceId != null) {
+                mCallbacks.dispatchOnBeginSynthesis(getCallerIdentity(), utteranceId, sampleRateInHz, audioFormat, channelCount);
+            }
+        }
+
+        @Override
+        public void dispatchOnAudioAvailable(byte[] audio) {
+            final String utteranceId = getUtteranceId();
+            if (utteranceId != null) {
+                mCallbacks.dispatchOnAudioAvailable(getCallerIdentity(), utteranceId, audio);
+            }
+        }
+
         abstract public String getUtteranceId();
 
         String getStringParam(Bundle params, String key, String defaultValue) {
@@ -1430,7 +1449,6 @@
             } catch (RemoteException e) {
                 Log.e(TAG, "Callback onStart failed: " + e);
             }
-
         }
 
         public void dispatchOnError(Object callerIdentity, String utteranceId,
@@ -1444,6 +1462,26 @@
             }
         }
 
+        public void dispatchOnBeginSynthesis(Object callerIdentity, String utteranceId, int sampleRateInHz, int audioFormat, int channelCount) {
+            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
+            if (cb == null) return;
+            try {
+                cb.onBeginSynthesis(utteranceId, sampleRateInHz, audioFormat, channelCount);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Callback dispatchOnBeginSynthesis(String, int, int, int) failed: " + e);
+            }
+        }
+
+        public void dispatchOnAudioAvailable(Object callerIdentity, String utteranceId, byte[] buffer) {
+            ITextToSpeechCallback cb = getCallbackFor(callerIdentity);
+            if (cb == null) return;
+            try {
+                cb.onAudioAvailable(utteranceId, buffer);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Callback dispatchOnAudioAvailable(String, byte[]) failed: " + e);
+            }
+        }
+
         @Override
         public void onCallbackDied(ITextToSpeechCallback callback, Object cookie) {
             IBinder caller = (IBinder) cookie;
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
index 890ea3d..72a5228 100644
--- a/core/java/android/speech/tts/UtteranceProgressListener.java
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -2,6 +2,8 @@
 
 package android.speech.tts;
 
+import android.media.AudioFormat;
+
 /**
  * Listener for events relating to the progress of an utterance through
  * the synthesis queue. Each utterance is associated with a call to
@@ -14,10 +16,10 @@
     /**
      * Called when an utterance "starts" as perceived by the caller. This will
      * be soon before audio is played back in the case of a {@link TextToSpeech#speak}
-     * or before the first bytes of a file are written to storage in the case
+     * or before the first bytes of a file are written to the file system in the case
      * of {@link TextToSpeech#synthesizeToFile}.
      *
-     * @param utteranceId the utterance ID of the utterance.
+     * @param utteranceId The utterance ID of the utterance.
      */
     public abstract void onStart(String utteranceId);
 
@@ -28,7 +30,7 @@
      *
      * This request is guaranteed to be called after {@link #onStart(String)}.
      *
-     * @param utteranceId the utterance ID of the utterance.
+     * @param utteranceId The utterance ID of the utterance.
      */
     public abstract void onDone(String utteranceId);
 
@@ -39,7 +41,7 @@
      * be a call to both {@link #onDone(String)} and {@link #onError(String)} for
      * the same utterance.
      *
-     * @param utteranceId the utterance ID of the utterance.
+     * @param utteranceId The utterance ID of the utterance.
      * @deprecated Use {@link #onError(String,int)} instead
      */
     @Deprecated
@@ -52,7 +54,7 @@
      * be a call to both {@link #onDone(String)} and {@link #onError(String,int)} for
      * the same utterance. The default implementation calls {@link #onError(String)}.
      *
-     * @param utteranceId the utterance ID of the utterance.
+     * @param utteranceId The utterance ID of the utterance.
      * @param errorCode one of the ERROR_* codes from {@link TextToSpeech}
      */
     public void onError(String utteranceId, int errorCode) {
@@ -65,7 +67,7 @@
      * or uses {@link TextToSpeech#QUEUE_FLUSH} as an argument with the
      * {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} methods.
      *
-     * @param utteranceId the utterance ID of the utterance.
+     * @param utteranceId The utterance ID of the utterance.
      * @param interrupted If true, then the utterance was interrupted while being synthesized
      *        and its output is incomplete. If false, then the utterance was flushed
      *        before the synthesis started.
@@ -74,6 +76,52 @@
     }
 
     /**
+     * Called when the TTS engine begins to synthesize the audio for a request.
+     *
+     * <p>
+     * It provides information about the format of the byte array for subsequent
+     * {@link #onAudioAvailable} calls.
+     * </p>
+     *
+     * <p>
+     * This is called when the TTS engine starts synthesizing audio for the request. If an
+     * application wishes to know when the audio is about to start playing, {#onStart(String)}
+     * should be used instead.
+     * </p>
+     *
+     * @param utteranceId The utterance ID of the utterance.
+     * @param sampleRateInHz Sample rate in hertz of the generated audio.
+     * @param audioFormat Audio format of the generated audio. Should be one of
+     *        {@link AudioFormat#ENCODING_PCM_8BIT}, {@link AudioFormat#ENCODING_PCM_16BIT} or
+     *        {@link AudioFormat#ENCODING_PCM_FLOAT}.
+     * @param channelCount The number of channels.
+     */
+    public void onBeginSynthesis(String utteranceId, int sampleRateInHz, int audioFormat, int channelCount) {
+    }
+
+    /**
+     * This is called when a chunk of audio is ready for consumption.
+     *
+     * <p>
+     * The audio parameter is a copy of what will be synthesized to the speakers (when synthesis was
+     * initiated with a {@link TextToSpeech#speak} call) or written to the file system (for
+     * {@link TextToSpeech#synthesizeToFile}). The audio bytes are delivered in one or more chunks;
+     * if {@link #onDone} or {@link #onError} is called all chunks have been received.
+     * </p>
+     *
+     * <p>
+     * The audio received here may not be played for some time depending on buffer sizes and the
+     * amount of items on the synthesis queue.
+     * </p>
+     *
+     * @param utteranceId The utterance ID of the utterance.
+     * @param audio A chunk of audio; the format can be known by listening to
+     *        {@link #onBeginSynthesis(String, int, int, int)}.
+     */
+    public void onAudioAvailable(String utteranceId, byte[] audio) {
+    }
+
+    /**
      * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new
      * progress listener.
      *
diff --git a/core/java/android/view/FrameStatsObserver.java b/core/java/android/view/FrameStatsObserver.java
new file mode 100644
index 0000000..0add607
--- /dev/null
+++ b/core/java/android/view/FrameStatsObserver.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 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 android.view;
+
+import android.annotation.NonNull;
+import android.util.Log;
+import android.os.Looper;
+import android.os.MessageQueue;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+import java.lang.NullPointerException;
+import java.lang.ref.WeakReference;
+import java.lang.SuppressWarnings;
+
+/**
+ * Provides streaming access to frame stats information from the rendering
+ * subsystem to apps.
+ *
+ * @hide
+ */
+public abstract class FrameStatsObserver {
+    private static final String TAG = "FrameStatsObserver";
+
+    private MessageQueue mMessageQueue;
+    private long[] mBuffer;
+
+    private FrameStats mFrameStats;
+
+    /* package */ ThreadedRenderer mRenderer;
+    /* package */ VirtualRefBasePtr mNative;
+
+    /**
+     * Containing class for frame statistics reported
+     * by the rendering subsystem.
+     */
+    public static class FrameStats {
+        /**
+         * Precise timing data for various milestones in a frame
+         * lifecycle.
+         *
+         * This data is exactly the same as what is returned by
+         * `adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats`
+         *
+         * The fields reported may change from release to release.
+         *
+         * @see {@link http://developer.android.com/training/testing/performance.html}
+         * for a description of the fields present.
+         */
+        public long[] mTimingData;
+    }
+
+    /**
+     * Creates a FrameStatsObserver
+     *
+     * @param looper the looper to use when invoking callbacks
+     */
+    public FrameStatsObserver(@NonNull Looper looper) {
+        if (looper == null) {
+            throw new NullPointerException("looper cannot be null");
+        }
+
+        mMessageQueue = looper.getQueue();
+        if (mMessageQueue == null) {
+            throw new IllegalStateException("invalid looper, null message queue\n");
+        }
+
+        mFrameStats = new FrameStats();
+    }
+
+    /**
+     * Called on provided looper when frame stats data is available
+     * for the previous frame.
+     *
+     * Clients of this class must do as little work as possible within
+     * this callback, as the buffer is shared between the producer and consumer.
+     *
+     * If the consumer is still executing within this method when there is new
+     * data available that data will be dropped. The producer cannot
+     * wait on the consumer.
+     *
+     * @param data the newly available data
+     */
+    public abstract void onDataAvailable(FrameStats data);
+
+    /**
+     * Returns the number of reports dropped as a result of a slow
+     * consumer.
+     */
+    public long getDroppedReportCount() {
+        if (mRenderer == null) {
+            return 0;
+        }
+
+        return mRenderer.getDroppedFrameReportCount();
+    }
+
+    public boolean isRegistered() {
+        return mRenderer != null && mNative != null;
+    }
+
+    // === called by native === //
+    @SuppressWarnings("unused")
+    private void notifyDataAvailable() {
+        mFrameStats.mTimingData = mBuffer;
+        onDataAvailable(mFrameStats);
+    }
+}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 0e4bc84..78a63a6 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -24,7 +24,9 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -34,12 +36,14 @@
 import android.view.View.AttachInfo;
 
 import com.android.internal.R;
+import com.android.internal.util.VirtualRefBasePtr;
 
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
 
 /**
  * Hardware renderer that proxies the rendering to a render thread. Most calls
@@ -339,6 +343,8 @@
     private boolean mEnabled;
     private boolean mRequested = true;
 
+    private HashSet<FrameStatsObserver> mFrameStatsObservers;
+
     ThreadedRenderer(Context context, boolean translucent) {
         final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
         mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
@@ -947,6 +953,31 @@
         }
     }
 
+    void addFrameStatsObserver(FrameStatsObserver fso) {
+        if (mFrameStatsObservers == null) {
+            mFrameStatsObservers = new HashSet<>();
+        }
+
+        long nativeFso = nAddFrameStatsObserver(mNativeProxy, fso);
+        fso.mRenderer = this;
+        fso.mNative = new VirtualRefBasePtr(nativeFso);
+        mFrameStatsObservers.add(fso);
+    }
+
+    void removeFrameStatsObserver(FrameStatsObserver fso) {
+        if (!mFrameStatsObservers.remove(fso)) {
+            throw new IllegalArgumentException("attempt to remove FrameStatsObserver that was never added");
+        }
+
+        nRemoveFrameStatsObserver(mNativeProxy, fso.mNative.get());
+        fso.mRenderer = null;
+        fso.mNative = null;
+    }
+
+    long getDroppedFrameReportCount() {
+        return nGetDroppedFrameReportCount(mNativeProxy);
+    }
+
     static native void setupShadersDiskCache(String cacheFile);
 
     private static native void nSetAtlas(long nativeProxy, GraphicBuffer buffer, long[] map);
@@ -1000,4 +1031,8 @@
     private static native void nDrawRenderNode(long nativeProxy, long rootRenderNode);
     private static native void nSetContentDrawBounds(long nativeProxy, int left,
              int top, int right, int bottom);
+
+    private static native long nAddFrameStatsObserver(long nativeProxy, FrameStatsObserver fso);
+    private static native void nRemoveFrameStatsObserver(long nativeProxy, long nativeFso);
+    private static native long nGetDroppedFrameReportCount(long nativeProxy);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index fff141a..6c3f308 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -110,6 +110,7 @@
 import com.google.android.collect.Lists;
 import com.google.android.collect.Maps;
 
+import java.lang.NullPointerException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
@@ -3702,6 +3703,11 @@
     private ViewPropertyAnimator mAnimator = null;
 
     /**
+     * List of FrameStatsObservers pending registration when mAttachInfo is null.
+     */
+    private ArrayList<FrameStatsObserver> mPendingFrameStatsObservers;
+
+    /**
      * Flag indicating that a drag can cross window boundaries.  When
      * {@link #startDragAndDrop(ClipData, DragShadowBuilder, Object, int)} is called
      * with this flag set, all visible applications will be able to participate
@@ -5381,6 +5387,58 @@
     }
 
     /**
+     * Set an observer to collect stats for each frame rendered for this view.
+     *
+     * @hide
+     */
+    public void addFrameStatsObserver(FrameStatsObserver fso) {
+        if (mAttachInfo != null) {
+            if (mAttachInfo.mHardwareRenderer != null) {
+                mAttachInfo.mHardwareRenderer.addFrameStatsObserver(fso);
+            } else {
+                Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
+            }
+        } else {
+            if (mPendingFrameStatsObservers == null) {
+                mPendingFrameStatsObservers = new ArrayList<>();
+            }
+
+            mPendingFrameStatsObservers.add(fso);
+        }
+    }
+
+    /**
+     * Remove observer configured to collect frame stats for this view.
+     *
+     * @hide
+     */
+    public void removeFrameStatsObserver(FrameStatsObserver fso) {
+        ThreadedRenderer renderer = getHardwareRenderer();
+
+        if (mPendingFrameStatsObservers != null) {
+            mPendingFrameStatsObservers.remove(fso);
+        }
+
+        if (renderer != null) {
+            renderer.removeFrameStatsObserver(fso);
+        }
+    }
+
+    private void registerPendingFrameStatsObservers() {
+        if (mPendingFrameStatsObservers != null) {
+            ThreadedRenderer renderer = getHardwareRenderer();
+            if (renderer != null) {
+                for (FrameStatsObserver fso : mPendingFrameStatsObservers) {
+                    renderer.addFrameStatsObserver(fso);
+                }
+            } else {
+                Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
+            }
+            mPendingFrameStatsObservers = null;
+        }
+    }
+
+    /**
      * Call this view's OnClickListener, if it is defined.  Performs all normal
      * actions associated with clicking: reporting accessibility event, playing
      * a sound, etc.
@@ -14903,6 +14961,9 @@
             info.mTreeObserver.merge(mFloatingTreeObserver);
             mFloatingTreeObserver = null;
         }
+
+        registerPendingFrameStatsObservers();
+
         if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
             mAttachInfo.mScrollContainers.add(this);
             mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index dfe0cc7..ee70891 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -34,6 +34,7 @@
 import android.media.session.MediaController;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -794,6 +795,40 @@
         return mCallback;
     }
 
+    /**
+     * Set an observer to collect frame stats for each frame rendererd in this window.
+     *
+     * Must be in hardware rendering mode.
+     * @hide
+     */
+    public final void addFrameStatsObserver(@NonNull FrameStatsObserver fso) {
+        final View decorView = getDecorView();
+        if (decorView == null) {
+            throw new IllegalStateException("can't observe a Window without an attached view");
+        }
+
+        if (fso == null) {
+            throw new NullPointerException("FrameStatsObserver cannot be null");
+        }
+
+        if (fso.isRegistered()) {
+            throw new IllegalStateException("FrameStatsObserver already registered on a Window.");
+        }
+
+        decorView.addFrameStatsObserver(fso);
+    }
+
+    /**
+     * Remove observer and stop listening to frame stats for this window.
+     * @hide
+     */
+    public final void removeFrameStatsObserver(FrameStatsObserver fso) {
+        final View decorView = getDecorView();
+        if (decorView != null) {
+            getDecorView().removeFrameStatsObserver(fso);
+        }
+    }
+
     /** @hide */
     public final void setOnWindowDismissedCallback(OnWindowDismissedCallback dcb) {
         mOnWindowDismissedCallback = dcb;
diff --git a/core/java/com/android/internal/logging/MetricsConstants.java b/core/java/com/android/internal/logging/MetricsConstants.java
index 37bf71c..9884585 100644
--- a/core/java/com/android/internal/logging/MetricsConstants.java
+++ b/core/java/com/android/internal/logging/MetricsConstants.java
@@ -22,8 +22,7 @@
  */
 public interface MetricsConstants {
     // These constants must match those in the analytic pipeline, do not edit.
-    // Add temporary values to the top of MetricsLogger instead.
-    public static final int VIEW_UNKNOWN = 0;
+    // define metric categories in frameworks/base/core/proto/src/metrics_constants.proto.
     public static final int MAIN_SETTINGS = 1;
     public static final int ACCESSIBILITY = 2;
     public static final int ACCESSIBILITY_CAPTION_PROPERTIES = 3;
@@ -65,7 +64,7 @@
     public static final int DEVELOPMENT = 39;
     public static final int DEVICEINFO = 40;
     public static final int DEVICEINFO_IMEI_INFORMATION = 41;
-    public static final int DEVICEINFO_MEMORY = 42;
+    public static final int DEVICEINFO_STORAGE = 42;
     public static final int DEVICEINFO_SIM_STATUS = 43;
     public static final int DEVICEINFO_STATUS = 44;
     public static final int DEVICEINFO_USB = 45;
@@ -134,7 +133,6 @@
     public static final int WIFI_INFO = 108;
     public static final int WIFI_P2P = 109;
     public static final int WIRELESS = 110;
-    public static final int QS_PANEL = 111;
     public static final int QS_AIRPLANEMODE = 112;
     public static final int QS_BLUETOOTH = 113;
     public static final int QS_CAST = 114;
@@ -282,10 +280,55 @@
     public static final int ACTION_WIGGLE_CAMERA_GESTURE = 256;
     public static final int QS_WORKMODE = 257;
     public static final int BACKGROUND_CHECK_SUMMARY = 258;
+    public static final int QS_LOCK_TILE = 259;
+    public static final int QS_USER_TILE = 260;
+    public static final int QS_BATTERY_TILE = 261;
+    public static final int NOTIFICATION_ZEN_MODE_VISUAL_INTERRUPTIONS = 262;
+    public static final int ACTION_ZEN_ALLOW_PEEK = 263;
+    public static final int ACTION_ZEN_ALLOW_LIGHTS = 264;
+    public static final int NOTIFICATION_TOPIC_NOTIFICATION = 265;
+    public static final int ACTION_DEFAULT_SMS_APP_CHANGED = 266;
+    public static final int QS_COLOR_MATRIX = 267;
+    public static final int QS_CUSTOM = 268;
+    public static final int ACTION_ZEN_ALLOW_SCREEN_ON = 269;
 
-    // These constants must match those in the analytic pipeline, do not edit.
-    // Add temporary values to the top of MetricsLogger instead.
+    /**
+     * Logged when the user docks a window from recents by longpressing a task and dragging it to
+     * the dock area.
+     */
+    public static final int ACTION_WINDOW_DOCK_DRAG_DROP = 270;
 
-    //aliases
-    public static final int DEVICEINFO_STORAGE = DEVICEINFO_MEMORY;
+    /**
+     * Logged when the user docks a fullscreen window by long pressing recents which also opens
+     * recents on the lower/right side.
+     */
+    public static final int ACTION_WINDOW_DOCK_LONGPRESS = 271;
+
+    /**
+     * Logged when the user docks a window by dragging from the navbar which also opens recents on
+     * the lower/right side.
+     */
+    public static final int ACTION_WINDOW_DOCK_SWIPE = 272;
+
+    /**
+     * Logged when the user launches a profile-specific app and we intercept it with the confirm
+     * credentials UI.
+     */
+    public static final int PROFILE_CHALLENGE = 273;
+    public static final int QS_BATTERY_DETAIL = 274;
+
+    /**
+     * Logged when the user goes into the overview history.
+     */
+    public static final int OVERVIEW_HISTORY = 275;
+
+    /**
+     * Logged when the user pages through overview.
+     */
+    public static final int ACTION_OVERVIEW_PAGE = 276;
+
+    /**
+     * Logged when the user launches a task from overview.
+     */
+    public static final int ACTION_OVERVIEW_SELECT = 277;
 }
diff --git a/core/java/com/android/internal/logging/MetricsLogger.java b/core/java/com/android/internal/logging/MetricsLogger.java
index b0b25d3..183d8d7 100644
--- a/core/java/com/android/internal/logging/MetricsLogger.java
+++ b/core/java/com/android/internal/logging/MetricsLogger.java
@@ -15,69 +15,21 @@
  */
 package com.android.internal.logging;
 
-
 import android.content.Context;
 import android.os.Build;
 import android.view.View;
 
+import com.android.internal.logging.MetricsProto.MetricsEvent;
+
 /**
  * Log all the things.
  *
  * @hide
  */
-public class MetricsLogger implements MetricsConstants {
-    // Temporary constants go here, to await migration to MetricsConstants.
-    public static final int QS_USER_TILE = 258;
-    public static final int QS_BATTERY_TILE = 259;
-    public static final int NOTIFICATION_ZEN_MODE_VISUAL_INTERRUPTIONS = 260;
-    public static final int ACTION_ZEN_ALLOW_PEEK = 261;
-    public static final int ACTION_ZEN_ALLOW_LIGHTS = 262;
-    public static final int NOTIFICATION_TOPIC_NOTIFICATION = 263;
-    public static final int ACTION_DEFAULT_SMS_APP_CHANGED = 264;
-    public static final int QS_COLOR_MATRIX = 265;
-    public static final int QS_CUSTOM = 266;
-    public static final int ACTION_ZEN_ALLOW_SCREEN_ON = 267;
+public class MetricsLogger implements com.android.internal.logging.MetricsConstants {
+    // define metric categories in frameworks/base/core/proto/src/metrics_constants.proto.
 
-    /**
-     * Logged when the user docks a window from recents by longpressing a task and dragging it to
-     * the dock area.
-     */
-    public static final int ACTION_WINDOW_DOCK_DRAG_DROP = 268;
-
-    /**
-     * Logged when the user docks a fullscreen window by long pressing recents which also opens
-     * recents on the lower/right side.
-     */
-    public static final int ACTION_WINDOW_DOCK_LONGPRESS = 269;
-
-    /**
-     * Logged when the user docks a window by dragging from the navbar which also opens recents on
-     * the lower/right side.
-     */
-    public static final int ACTION_WINDOW_DOCK_SWIPE = 270;
-
-    /**
-     * Logged when the user launches a profile-specific app and we intercept it with the confirm
-     * credentials UI.
-     */
-    public static final int PROFILE_CHALLENGE = 271;
-    public static final int QS_BATTERY_DETAIL = 272;
-
-    /**
-     * Logged when the user goes into the overview history.
-     */
-    public static final int OVERVIEW_HISTORY = 273;
-
-    /**
-     * Logged when the user pages through overview.
-     */
-    public static final int ACTION_OVERVIEW_PAGE = 274;
-
-    /**
-     * Logged when the user launches a task from overview.
-     */
-    public static final int ACTION_OVERVIEW_SELECT = 275;
-
+    public static final int VIEW_UNKNOWN = MetricsEvent.VIEW_UNKNOWN;
 
     public static void visible(Context context, int category) throws IllegalArgumentException {
         if (Build.IS_DEBUGGABLE && category == VIEW_UNKNOWN) {
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 5aa6a73..edced56 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -28,14 +28,18 @@
 #include <EGL/eglext.h>
 #include <EGL/egl_cache.h>
 
+#include <utils/Looper.h>
+#include <utils/RefBase.h>
 #include <utils/StrongPointer.h>
 #include <android_runtime/android_view_Surface.h>
 #include <system/window.h>
 
 #include "android_view_GraphicBuffer.h"
+#include "android_os_MessageQueue.h"
 
 #include <Animator.h>
 #include <AnimationContext.h>
+#include <FrameInfo.h>
 #include <IContextFactory.h>
 #include <JankTracker.h>
 #include <RenderNode.h>
@@ -50,6 +54,12 @@
 using namespace android::uirenderer;
 using namespace android::uirenderer::renderthread;
 
+struct {
+    jfieldID buffer;
+    jfieldID messageQueue;
+    jmethodID notifyData;
+} gFrameStatsObserverClassInfo;
+
 static JNIEnv* getenv(JavaVM* vm) {
     JNIEnv* env;
     if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
@@ -207,6 +217,99 @@
     RootRenderNode* mRootNode;
 };
 
+class ObserverProxy;
+
+class NotifyHandler : public MessageHandler {
+public:
+    NotifyHandler(JavaVM* vm) : mVm(vm) {}
+
+    void setObserver(ObserverProxy* observer) {
+        mObserver = observer;
+    }
+
+    void setBuffer(BufferPool::Buffer* buffer) {
+        mBuffer = buffer;
+    }
+
+    virtual void handleMessage(const Message& message);
+
+private:
+    JavaVM* mVm;
+
+    sp<ObserverProxy> mObserver;
+    BufferPool::Buffer* mBuffer;
+};
+
+class ObserverProxy : public FrameStatsObserver {
+public:
+    ObserverProxy(JavaVM *vm, jobject fso) : mVm(vm) {
+        JNIEnv* env = getenv(mVm);
+
+        jlongArray longArrayLocal = env->NewLongArray(kBufferSize);
+        LOG_ALWAYS_FATAL_IF(longArrayLocal == nullptr,
+                "OOM: can't allocate frame stats buffer");
+        env->SetObjectField(fso, gFrameStatsObserverClassInfo.buffer, longArrayLocal);
+
+        mFsoWeak = env->NewWeakGlobalRef(fso);
+        LOG_ALWAYS_FATAL_IF(mFsoWeak == nullptr,
+                "unable to create frame stats observer reference");
+
+        jobject messageQueueLocal =
+                env->GetObjectField(fso, gFrameStatsObserverClassInfo.messageQueue);
+        mMessageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueLocal);
+        LOG_ALWAYS_FATAL_IF(mMessageQueue == nullptr, "message queue not available");
+
+        mMessageHandler = new NotifyHandler(mVm);
+        LOG_ALWAYS_FATAL_IF(mMessageHandler == nullptr,
+                "OOM: unable to allocate NotifyHandler");
+    }
+
+    ~ObserverProxy() {
+        JNIEnv* env = getenv(mVm);
+        env->DeleteWeakGlobalRef(mFsoWeak);
+    }
+
+    jweak getJavaObjectRef() {
+        return mFsoWeak;
+    }
+
+    virtual void notify(BufferPool::Buffer* buffer) {
+        buffer->incRef();
+        mMessageHandler->setBuffer(buffer);
+        mMessageHandler->setObserver(this);
+        mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
+    }
+
+private:
+    static const int kBufferSize = static_cast<int>(FrameInfoIndex::NumIndexes);
+
+    JavaVM* mVm;
+    jweak mFsoWeak;
+
+    sp<MessageQueue> mMessageQueue;
+    sp<NotifyHandler> mMessageHandler;
+    Message mMessage;
+};
+
+void NotifyHandler::handleMessage(const Message& message) {
+    JNIEnv* env = getenv(mVm);
+
+    jobject target = env->NewLocalRef(mObserver->getJavaObjectRef());
+
+    if (target != nullptr) {
+        jobject javaBuffer = env->GetObjectField(target, gFrameStatsObserverClassInfo.buffer);
+        if (javaBuffer != nullptr) {
+            env->SetLongArrayRegion(reinterpret_cast<jlongArray>(javaBuffer),
+                    0, mBuffer->getSize(), mBuffer->getBuffer());
+            env->CallVoidMethod(target, gFrameStatsObserverClassInfo.notifyData);
+            env->DeleteLocalRef(target);
+        }
+    }
+
+    mBuffer->release();
+    mObserver.clear();
+}
+
 static void android_view_ThreadedRenderer_setAtlas(JNIEnv* env, jobject clazz,
         jlong proxyPtr, jobject graphicBuffer, jlongArray atlasMapArray) {
     sp<GraphicBuffer> buffer = graphicBufferForJavaObject(env, graphicBuffer);
@@ -468,6 +571,42 @@
 }
 
 // ----------------------------------------------------------------------------
+// FrameStatsObserver
+// ----------------------------------------------------------------------------
+
+static jlong android_view_ThreadedRenderer_addFrameStatsObserver(JNIEnv* env,
+        jclass clazz, jlong proxyPtr, jobject fso) {
+    JavaVM* vm = nullptr;
+    if (env->GetJavaVM(&vm) != JNI_OK) {
+        LOG_ALWAYS_FATAL("Unable to get Java VM");
+        return 0;
+    }
+
+    renderthread::RenderProxy* renderProxy =
+            reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
+
+    FrameStatsObserver* observer = new ObserverProxy(vm, fso);
+    renderProxy->addFrameStatsObserver(observer);
+    return reinterpret_cast<jlong>(observer);
+}
+
+static void android_view_ThreadedRenderer_removeFrameStatsObserver(JNIEnv* env, jclass clazz,
+        jlong proxyPtr, jlong observerPtr) {
+    FrameStatsObserver* observer = reinterpret_cast<FrameStatsObserver*>(observerPtr);
+    renderthread::RenderProxy* renderProxy =
+            reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
+
+    renderProxy->removeFrameStatsObserver(observer);
+}
+
+static jint android_view_ThreadedRenderer_getDroppedFrameReportCount(JNIEnv* env, jclass clazz,
+        jlong proxyPtr) {
+    renderthread::RenderProxy* renderProxy =
+            reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
+    return renderProxy->getDroppedFrameReportCount();
+}
+
+// ----------------------------------------------------------------------------
 // Shaders
 // ----------------------------------------------------------------------------
 
@@ -523,9 +662,26 @@
     { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
     { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
     { "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
+    { "nAddFrameStatsObserver",
+            "(JLandroid/view/FrameStatsObserver;)J",
+            (void*)android_view_ThreadedRenderer_addFrameStatsObserver },
+    { "nRemoveFrameStatsObserver",
+            "(JJ)V",
+            (void*)android_view_ThreadedRenderer_removeFrameStatsObserver },
+    { "nGetDroppedFrameReportCount",
+            "(J)J",
+            (void*)android_view_ThreadedRenderer_getDroppedFrameReportCount },
 };
 
 int register_android_view_ThreadedRenderer(JNIEnv* env) {
+    jclass clazz = FindClassOrDie(env, "android/view/FrameStatsObserver");
+    gFrameStatsObserverClassInfo.messageQueue  =
+            GetFieldIDOrDie(env, clazz, "mMessageQueue", "Landroid/os/MessageQueue;");
+    gFrameStatsObserverClassInfo.buffer =
+            GetFieldIDOrDie(env, clazz, "mBuffer", "[J");
+    gFrameStatsObserverClassInfo.notifyData =
+            GetMethodIDOrDie(env, clazz, "notifyDataAvailable", "()V");
+
     return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
 }
 
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index 8ba6318..483ccf7 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -235,7 +235,8 @@
     tests/unit/LinearAllocatorTests.cpp \
     tests/unit/VectorDrawableTests.cpp \
     tests/unit/OffscreenBufferPoolTests.cpp \
-    tests/unit/StringUtilsTests.cpp
+    tests/unit/StringUtilsTests.cpp \
+    tests/unit/BufferPoolTests.cpp
 
 ifeq (true, $(HWUI_NEW_OPS))
     LOCAL_SRC_FILES += \
diff --git a/libs/hwui/BufferPool.h b/libs/hwui/BufferPool.h
new file mode 100644
index 0000000..9bda233
--- /dev/null
+++ b/libs/hwui/BufferPool.h
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <utils/RefBase.h>
+#include <utils/Log.h>
+
+#include <atomic>
+#include <stdint.h>
+#include <memory>
+#include <mutex>
+
+namespace android {
+namespace uirenderer {
+
+/*
+ * Simple thread-safe pool of int64_t arrays of a provided size.
+ *
+ * Permits allocating a client-provided max number of buffers.
+ * If all buffers are in use, refuses to service any more
+ * acquire requests until buffers are re-released to the pool.
+ */
+class BufferPool : public VirtualLightRefBase {
+public:
+    class Buffer {
+    public:
+        int64_t* getBuffer() { return mBuffer.get(); }
+        size_t getSize() { return mSize; }
+
+        void release() {
+            LOG_ALWAYS_FATAL_IF(mPool.get() == nullptr, "attempt to release unacquired buffer");
+            mPool->release(this);
+        }
+
+        Buffer* incRef() {
+            mRefs++;
+            return this;
+        }
+
+        int decRef() {
+            int refs = mRefs.fetch_sub(1);
+            LOG_ALWAYS_FATAL_IF(refs == 0, "buffer reference decremented below 0");
+            return refs - 1;
+        }
+
+    private:
+        friend class BufferPool;
+
+        Buffer(BufferPool* pool, size_t size) {
+            mSize = size;
+            mBuffer.reset(new int64_t[size]);
+            mPool = pool;
+            mRefs++;
+        }
+
+        void setPool(BufferPool* pool) {
+            mPool = pool;
+        }
+
+        std::unique_ptr<Buffer> mNext;
+        std::unique_ptr<int64_t[]> mBuffer;
+        sp<BufferPool> mPool;
+        size_t mSize;
+
+        std::atomic_int mRefs;
+    };
+
+    BufferPool(size_t bufferSize, size_t count)
+            : mBufferSize(bufferSize), mCount(count) {}
+
+    /**
+     * Acquires a buffer from the buffer pool if available.
+     *
+     * Only `mCount` buffers are allowed to be in use at a single
+     * instance.
+     *
+     * If no buffer is available, i.e. `mCount` buffers are in use,
+     * returns nullptr.
+     *
+     * The pointer returned from this method *MUST NOT* be freed, instead
+     * BufferPool::release() must be called upon it when the client
+     * is done with it. Failing to release buffers will eventually make the
+     * BufferPool refuse to service any more BufferPool::acquire() requests.
+     */
+    BufferPool::Buffer* acquire() {
+        std::lock_guard<std::mutex> lock(mLock);
+
+        if (mHead.get() != nullptr) {
+            BufferPool::Buffer* res = mHead.release();
+            mHead = std::move(res->mNext);
+            res->mNext.reset(nullptr);
+            res->setPool(this);
+            res->incRef();
+            return res;
+        }
+
+        if (mAllocatedCount < mCount) {
+            ++mAllocatedCount;
+            return new BufferPool::Buffer(this, mBufferSize);
+        }
+
+        return nullptr;
+    }
+
+    /**
+     * Releases a buffer previously acquired by BufferPool::acquire().
+     *
+     * The released buffer is not valid after calling this method and
+     * attempting to use will result in undefined behavior.
+     */
+    void release(BufferPool::Buffer* buffer) {
+        std::lock_guard<std::mutex> lock(mLock);
+
+        if (buffer->decRef() != 0) {
+            return;
+        }
+
+        buffer->setPool(nullptr);
+
+        BufferPool::Buffer* list = mHead.get();
+        if (list == nullptr) {
+            mHead.reset(buffer);
+            mHead->mNext.reset(nullptr);
+            return;
+        }
+
+        while (list->mNext.get() != nullptr) {
+            list = list->mNext.get();
+        }
+
+        list->mNext.reset(buffer);
+    }
+
+    /*
+     * Used for testing.
+     */
+    size_t getAvailableBufferCount() {
+        size_t remainingToAllocateCount = mCount - mAllocatedCount;
+
+        BufferPool::Buffer* list = mHead.get();
+        if (list == nullptr) return remainingToAllocateCount;
+
+        int count = 1;
+        while (list->mNext.get() != nullptr) {
+            count++;
+            list = list->mNext.get();
+        }
+
+        return count + remainingToAllocateCount;
+    }
+
+private:
+    mutable std::mutex mLock;
+
+    size_t mBufferSize;
+    size_t mCount;
+    size_t mAllocatedCount = 0;
+    std::unique_ptr<BufferPool::Buffer> mHead;
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/FrameInfo.h b/libs/hwui/FrameInfo.h
index f8013ab..0baca39 100644
--- a/libs/hwui/FrameInfo.h
+++ b/libs/hwui/FrameInfo.h
@@ -118,6 +118,10 @@
         set(FrameInfoIndex::Flags) |= static_cast<uint64_t>(frameInfoFlag);
     }
 
+    const int64_t* data() const {
+        return mFrameInfo;
+    }
+
     inline int64_t operator[](FrameInfoIndex index) const {
         return get(index);
     }
diff --git a/libs/hwui/FrameStatsObserver.h b/libs/hwui/FrameStatsObserver.h
new file mode 100644
index 0000000..7abc9f1
--- /dev/null
+++ b/libs/hwui/FrameStatsObserver.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <utils/RefBase.h>
+
+#include "BufferPool.h"
+
+namespace android {
+namespace uirenderer {
+
+class FrameStatsObserver : public VirtualLightRefBase {
+public:
+    virtual void notify(BufferPool::Buffer* buffer);
+};
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/FrameStatsReporter.h b/libs/hwui/FrameStatsReporter.h
new file mode 100644
index 0000000..b8a9432
--- /dev/null
+++ b/libs/hwui/FrameStatsReporter.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#pragma once
+
+#include <utils/RefBase.h>
+#include <utils/Log.h>
+
+#include "BufferPool.h"
+#include "FrameInfo.h"
+#include "FrameStatsObserver.h"
+
+#include <string.h>
+#include <vector>
+
+namespace android {
+namespace uirenderer {
+
+class FrameStatsReporter {
+public:
+    FrameStatsReporter() {
+        mBufferPool = new BufferPool(kBufferSize, kBufferCount);
+        LOG_ALWAYS_FATAL_IF(mBufferPool.get() == nullptr, "OOM: unable to allocate buffer pool");
+    }
+
+    void addObserver(FrameStatsObserver* observer) {
+        mObservers.push_back(observer);
+    }
+
+    bool removeObserver(FrameStatsObserver* observer) {
+        for (size_t i = 0; i < mObservers.size(); i++) {
+            if (mObservers[i].get() == observer) {
+                mObservers.erase(mObservers.begin() + i);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    bool hasObservers() {
+        return mObservers.size() > 0;
+    }
+
+    void reportFrameStats(const int64_t* stats) {
+        BufferPool::Buffer* statsBuffer = mBufferPool->acquire();
+
+        if (statsBuffer != nullptr) {
+            // copy in frame stats
+            memcpy(statsBuffer->getBuffer(), stats, kBufferSize * sizeof(*stats));
+
+            // notify on requested threads
+            for (size_t i = 0; i < mObservers.size(); i++) {
+                mObservers[i]->notify(statsBuffer);
+            }
+
+            // drop our reference
+            statsBuffer->release();
+        } else {
+            mDroppedReports++;
+        }
+    }
+
+    int getDroppedReports() { return mDroppedReports; }
+
+private:
+    static const size_t kBufferCount = 3;
+    static const size_t kBufferSize = static_cast<size_t>(FrameInfoIndex::NumIndexes);
+
+    std::vector< sp<FrameStatsObserver> > mObservers;
+
+    sp<BufferPool> mBufferPool;
+
+    int mDroppedReports = 0;
+};
+
+}; // namespace uirenderer
+}; // namespace android
+
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 6f8d627..cdd2da0 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -505,6 +505,9 @@
 
     mJankTracker.addFrame(*mCurrentFrameInfo);
     mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
+    if (CC_UNLIKELY(mFrameStatsReporter.get() != nullptr)) {
+        mFrameStatsReporter->reportFrameStats(mCurrentFrameInfo->data());
+    }
 
     GpuMemoryTracker::onFrameCompleted();
 }
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 8e64cbb..270fb1f 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -20,6 +20,7 @@
 #include "DamageAccumulator.h"
 #include "FrameInfo.h"
 #include "FrameInfoVisualizer.h"
+#include "FrameStatsReporter.h"
 #include "IContextFactory.h"
 #include "LayerUpdateQueue.h"
 #include "RenderNode.h"
@@ -139,6 +140,31 @@
         return mRenderThread.renderState();
     }
 
+    void addFrameStatsObserver(FrameStatsObserver* observer) {
+        if (mFrameStatsReporter.get() == nullptr) {
+            mFrameStatsReporter.reset(new FrameStatsReporter());
+        }
+
+        mFrameStatsReporter->addObserver(observer);
+    }
+
+    void removeFrameStatsObserver(FrameStatsObserver* observer) {
+        if (mFrameStatsReporter.get() != nullptr) {
+            mFrameStatsReporter->removeObserver(observer);
+            if (!mFrameStatsReporter->hasObservers()) {
+                mFrameStatsReporter.reset(nullptr);
+            }
+        }
+    }
+
+    long getDroppedFrameReportCount() {
+        if (mFrameStatsReporter.get() != nullptr) {
+            return mFrameStatsReporter->getDroppedReports();
+        }
+
+        return 0;
+    }
+
 private:
     friend class RegisterFrameCallbackTask;
     // TODO: Replace with something better for layer & other GL object
@@ -187,6 +213,7 @@
     std::string mName;
     JankTracker mJankTracker;
     FrameInfoVisualizer mProfiler;
+    std::unique_ptr<FrameStatsReporter> mFrameStatsReporter;
 
     std::set<RenderNode*> mPrefetechedLayers;
 
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index db2a2c8..1d1b144 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -568,6 +568,54 @@
     post(task);
 }
 
+CREATE_BRIDGE2(addFrameStatsObserver, CanvasContext* context,
+        FrameStatsObserver* frameStatsObserver) {
+   args->context->addFrameStatsObserver(args->frameStatsObserver);
+   if (args->frameStatsObserver != nullptr) {
+       args->frameStatsObserver->decStrong(args->context);
+   }
+   return nullptr;
+}
+
+void RenderProxy::addFrameStatsObserver(FrameStatsObserver* observer) {
+    SETUP_TASK(addFrameStatsObserver);
+    args->context = mContext;
+    args->frameStatsObserver = observer;
+    if (observer != nullptr) {
+        observer->incStrong(mContext);
+    }
+    post(task);
+}
+
+CREATE_BRIDGE2(removeFrameStatsObserver, CanvasContext* context,
+        FrameStatsObserver* frameStatsObserver) {
+   args->context->removeFrameStatsObserver(args->frameStatsObserver);
+   if (args->frameStatsObserver != nullptr) {
+       args->frameStatsObserver->decStrong(args->context);
+   }
+   return nullptr;
+}
+
+void RenderProxy::removeFrameStatsObserver(FrameStatsObserver* observer) {
+    SETUP_TASK(removeFrameStatsObserver);
+    args->context = mContext;
+    args->frameStatsObserver = observer;
+    if (observer != nullptr) {
+        observer->incStrong(mContext);
+    }
+    post(task);
+}
+
+CREATE_BRIDGE1(getDroppedFrameReportCount, CanvasContext* context) {
+    return (void*) args->context->getDroppedFrameReportCount();
+}
+
+long RenderProxy::getDroppedFrameReportCount() {
+    SETUP_TASK(getDroppedFrameReportCount);
+    args->context = mContext;
+    return (long) postAndWait(task);
+}
+
 void RenderProxy::post(RenderTask* task) {
     mRenderThread.queue(task);
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 0f91b2a..4180d802 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -29,6 +29,7 @@
 #include <utils/StrongPointer.h>
 
 #include "../Caches.h"
+#include "../FrameStatsObserver.h"
 #include "../IContextFactory.h"
 #include "CanvasContext.h"
 #include "DrawFrameTask.h"
@@ -112,6 +113,10 @@
     ANDROID_API void drawRenderNode(RenderNode* node);
     ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
 
+    ANDROID_API void addFrameStatsObserver(FrameStatsObserver* observer);
+    ANDROID_API void removeFrameStatsObserver(FrameStatsObserver* observer);
+    ANDROID_API long getDroppedFrameReportCount();
+
 private:
     RenderThread& mRenderThread;
     CanvasContext* mContext;
diff --git a/libs/hwui/tests/unit/BufferPoolTests.cpp b/libs/hwui/tests/unit/BufferPoolTests.cpp
new file mode 100644
index 0000000..09bd302
--- /dev/null
+++ b/libs/hwui/tests/unit/BufferPoolTests.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include <BufferPool.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+namespace uirenderer {
+
+TEST(BufferPool, acquireThenRelease) {
+    static const int numRuns = 5;
+
+    // 10 buffers of size 1
+    static const size_t bufferSize = 1;
+    static const size_t bufferCount = 10;
+    sp<BufferPool> pool = new BufferPool(bufferSize, bufferCount);
+
+    for (int run = 0; run < numRuns; run++) {
+        BufferPool::Buffer* acquiredBuffers[bufferCount];
+        for (size_t i = 0; i < bufferCount; i++) {
+            ASSERT_EQ(bufferCount - i, pool->getAvailableBufferCount());
+            acquiredBuffers[i] = pool->acquire();
+            ASSERT_NE(nullptr, acquiredBuffers[i]);
+        }
+
+        for (size_t i = 0; i < bufferCount; i++) {
+            ASSERT_EQ(i, pool->getAvailableBufferCount());
+            acquiredBuffers[i]->release();
+            acquiredBuffers[i] = nullptr;
+        }
+
+        ASSERT_EQ(bufferCount, pool->getAvailableBufferCount());
+    }
+}
+
+TEST(BufferPool, acquireReleaseInterleaved) {
+    static const int numRuns = 5;
+
+    // 10 buffers of size 1
+    static const size_t bufferSize = 1;
+    static const size_t bufferCount = 10;
+
+    sp<BufferPool> pool = new BufferPool(bufferSize, bufferCount);
+
+    for (int run = 0; run < numRuns; run++) {
+        BufferPool::Buffer* acquiredBuffers[bufferCount];
+
+        // acquire all
+        for (size_t i = 0; i < bufferCount; i++) {
+            ASSERT_EQ(bufferCount - i, pool->getAvailableBufferCount());
+            acquiredBuffers[i] = pool->acquire();
+            ASSERT_NE(nullptr, acquiredBuffers[i]);
+        }
+
+        // release half
+        for (size_t i = 0; i < bufferCount / 2; i++) {
+            ASSERT_EQ(i, pool->getAvailableBufferCount());
+            acquiredBuffers[i]->release();
+            acquiredBuffers[i] = nullptr;
+        }
+
+        const size_t expectedRemaining = bufferCount / 2;
+        ASSERT_EQ(expectedRemaining, pool->getAvailableBufferCount());
+
+        // acquire half
+        for (size_t i = 0; i < bufferCount / 2; i++) {
+            ASSERT_EQ(expectedRemaining - i, pool->getAvailableBufferCount());
+            acquiredBuffers[i] = pool->acquire();
+        }
+
+        // acquire one more, should fail
+        ASSERT_EQ(nullptr, pool->acquire());
+
+        // release all
+        for (size_t i = 0; i < bufferCount; i++) {
+            ASSERT_EQ(i, pool->getAvailableBufferCount());
+            acquiredBuffers[i]->release();
+            acquiredBuffers[i] = nullptr;
+        }
+
+        ASSERT_EQ(bufferCount, pool->getAvailableBufferCount());
+    }
+}
+
+};
+};
diff --git a/libs/hwui/utils/TestWindowContext.cpp b/libs/hwui/utils/TestWindowContext.cpp
index dcc4946..b0431ce 100644
--- a/libs/hwui/utils/TestWindowContext.cpp
+++ b/libs/hwui/utils/TestWindowContext.cpp
@@ -138,8 +138,8 @@
 
         // Move the pixels into the destination SkBitmap
 
-        SK_ALWAYSBREAK(nativeBuffer.format == android::PIXEL_FORMAT_RGBA_8888 &&
-                       "Native buffer not RGBA!");
+        LOG_ALWAYS_FATAL_IF(nativeBuffer.format != android::PIXEL_FORMAT_RGBA_8888,
+                            "Native buffer not RGBA!");
         SkImageInfo nativeConfig =
             SkImageInfo::Make(nativeBuffer.width, nativeBuffer.height,
                               kRGBA_8888_SkColorType, kPremul_SkAlphaType);
@@ -153,8 +153,8 @@
             return false;
         }
 
-        SK_ALWAYSBREAK(bmp->colorType() == kRGBA_8888_SkColorType &&
-                       "Destination buffer not RGBA!");
+        LOG_ALWAYS_FATAL_IF(bmp->colorType() != kRGBA_8888_SkColorType,
+                            "Destination buffer not RGBA!");
         success =
             nativeWrapper.readPixels(destinationConfig, bmp->getPixels(), bmp->rowBytes(), 0, 0);
         if (!success) {
diff --git a/packages/SystemUI/Android.mk b/packages/SystemUI/Android.mk
index 61cad2f..e4d0fec 100644
--- a/packages/SystemUI/Android.mk
+++ b/packages/SystemUI/Android.mk
@@ -11,7 +11,8 @@
     android-support-v7-recyclerview \
     android-support-v7-preference \
     android-support-v7-appcompat \
-    android-support-v14-preference
+    android-support-v14-preference \
+    framework-protos
 
 LOCAL_JAVA_LIBRARIES := telephony-common
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index b5f146b..8979023 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -34,7 +34,9 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
+
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
 import com.android.systemui.qs.QSTile.DetailAdapter;
@@ -245,7 +247,7 @@
     public void setExpanded(boolean expanded) {
         if (mExpanded == expanded) return;
         mExpanded = expanded;
-        MetricsLogger.visibility(mContext, MetricsLogger.QS_PANEL, mExpanded);
+        MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, mExpanded);
         if (!mExpanded) {
             closeDetail();
         } else {
@@ -498,7 +500,7 @@
         int newVis = visible ? VISIBLE : INVISIBLE;
         mQsContainer.setVisibility(newVis);
         if (mGridContentVisible != visible) {
-            MetricsLogger.visibility(mContext, MetricsLogger.QS_PANEL, newVis);
+            MetricsLogger.visibility(mContext, MetricsEvent.QS_PANEL, newVis);
         }
         mGridContentVisible = visible;
     }
diff --git a/proto/Android.mk b/proto/Android.mk
new file mode 100644
index 0000000..a13a780
--- /dev/null
+++ b/proto/Android.mk
@@ -0,0 +1,16 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := framework-protos
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_SRC_FILES:= $(call all-proto-files-under, src)
+LOCAL_JARJAR_RULES := $(LOCAL_PATH)/jarjar-rules.txt
+
+LOCAL_NO_STANDARD_LIBRARIES := true
+LOCAL_JAVA_LIBRARIES := core-oj core-libart
+
+LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk $(LOCAL_PATH)/jarjar-rules.txt
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/proto/jarjar-rules.txt b/proto/jarjar-rules.txt
new file mode 100644
index 0000000..0c77c2a
--- /dev/null
+++ b/proto/jarjar-rules.txt
@@ -0,0 +1 @@
+rule com.google.** com.android.@1
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
new file mode 100644
index 0000000..fa3a47d
--- /dev/null
+++ b/proto/src/metrics_constants.proto
@@ -0,0 +1,325 @@
+// Copyright (C) 2016 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.
+
+syntax = "proto2";
+
+option java_package = "com.android.internal.logging";
+option java_outer_classname = "MetricsProto";
+
+package com_android_internal_logging;
+
+// Wrapper for System UI log events
+message MetricsEvent {
+
+  // Known visual elements: views or controls.
+  enum View {
+    VIEW_UNKNOWN = 0;
+    MAIN_SETTINGS = 1;
+    ACCESSIBILITY = 2;
+    ACCESSIBILITY_CAPTION_PROPERTIES = 3;
+    ACCESSIBILITY_SERVICE = 4;
+    ACCESSIBILITY_TOGGLE_DALTONIZER = 5;
+    ACCESSIBILITY_TOGGLE_GLOBAL_GESTURE = 6;
+    ACCESSIBILITY_TOGGLE_SCREEN_MAGNIFICATION = 7;
+    ACCOUNT = 8;
+    ACCOUNTS_ACCOUNT_SYNC = 9;
+    ACCOUNTS_CHOOSE_ACCOUNT_ACTIVITY = 10;
+    ACCOUNTS_MANAGE_ACCOUNTS = 11;
+    APN = 12;
+    APN_EDITOR = 13;
+    APP_OPS_DETAILS = 14;
+    APP_OPS_SUMMARY = 15;
+    APPLICATION = 16;
+    APPLICATIONS_APP_LAUNCH = 17;
+    APPLICATIONS_APP_PERMISSION = 18;
+    APPLICATIONS_APP_STORAGE = 19;
+    APPLICATIONS_INSTALLED_APP_DETAILS = 20;
+    APPLICATIONS_PROCESS_STATS_DETAIL = 21;
+    APPLICATIONS_PROCESS_STATS_MEM_DETAIL = 22;
+    APPLICATIONS_PROCESS_STATS_UI = 23;
+    BLUETOOTH = 24;
+    BLUETOOTH_DEVICE_PICKER = 25;
+    BLUETOOTH_DEVICE_PROFILES = 26;
+    CHOOSE_LOCK_GENERIC = 27;
+    CHOOSE_LOCK_PASSWORD = 28;
+    CHOOSE_LOCK_PATTERN = 29;
+    CONFIRM_LOCK_PASSWORD = 30;
+    CONFIRM_LOCK_PATTERN = 31;
+    CRYPT_KEEPER = 32;
+    CRYPT_KEEPER_CONFIRM = 33;
+    DASHBOARD_SEARCH_RESULTS = 34;
+    DASHBOARD_SUMMARY = 35;
+    DATA_USAGE = 36;
+    DATA_USAGE_SUMMARY = 37;
+    DATE_TIME = 38;
+    DEVELOPMENT = 39;
+    DEVICEINFO = 40;
+    DEVICEINFO_IMEI_INFORMATION = 41;
+    DEVICEINFO_STORAGE = 42;
+    DEVICEINFO_SIM_STATUS = 43;
+    DEVICEINFO_STATUS = 44;
+    DEVICEINFO_USB = 45;
+    DISPLAY = 46;
+    DREAM = 47;
+    ENCRYPTION = 48;
+    FINGERPRINT = 49;
+    FINGERPRINT_ENROLL = 50;
+    FUELGAUGE_BATTERY_HISTORY_DETAIL = 51;
+    FUELGAUGE_BATTERY_SAVER = 52;
+    FUELGAUGE_POWER_USAGE_DETAIL = 53;
+    FUELGAUGE_POWER_USAGE_SUMMARY = 54;
+    HOME = 55;
+    ICC_LOCK = 56;
+    INPUTMETHOD_LANGUAGE = 57;
+    INPUTMETHOD_KEYBOARD = 58;
+    INPUTMETHOD_SPELL_CHECKERS = 59;
+    INPUTMETHOD_SUBTYPE_ENABLER = 60;
+    INPUTMETHOD_USER_DICTIONARY = 61;
+    INPUTMETHOD_USER_DICTIONARY_ADD_WORD = 62;
+    LOCATION = 63;
+    LOCATION_MODE = 64;
+    MANAGE_APPLICATIONS = 65;
+    MASTER_CLEAR = 66;
+    MASTER_CLEAR_CONFIRM = 67;
+    NET_DATA_USAGE_METERED = 68;
+    NFC_BEAM = 69;
+    NFC_PAYMENT = 70;
+    NOTIFICATION = 71;
+    NOTIFICATION_APP_NOTIFICATION = 72;
+    NOTIFICATION_OTHER_SOUND = 73;
+    NOTIFICATION_REDACTION = 74;
+    NOTIFICATION_STATION = 75;
+    NOTIFICATION_ZEN_MODE = 76;
+    OWNER_INFO = 77;
+    PRINT_JOB_SETTINGS = 78;
+    PRINT_SERVICE_SETTINGS = 79;
+    PRINT_SETTINGS = 80;
+    PRIVACY = 81;
+    PROXY_SELECTOR = 82;
+    RESET_NETWORK = 83;
+    RESET_NETWORK_CONFIRM = 84;
+    RUNNING_SERVICE_DETAILS = 85;
+    SCREEN_PINNING = 86;
+    SECURITY = 87;
+    SIM = 88;
+    TESTING = 89;
+    TETHER = 90;
+    TRUST_AGENT = 91;
+    TRUSTED_CREDENTIALS = 92;
+    TTS_ENGINE_SETTINGS = 93;
+    TTS_TEXT_TO_SPEECH = 94;
+    USAGE_ACCESS = 95;
+    USER = 96;
+    USERS_APP_RESTRICTIONS = 97;
+    USER_DETAILS = 98;
+    VOICE_INPUT = 99;
+    VPN = 100;
+    WALLPAPER_TYPE = 101;
+    WFD_WIFI_DISPLAY = 102;
+    WIFI = 103;
+    WIFI_ADVANCED = 104;
+    WIFI_CALLING = 105;
+    WIFI_SAVED_ACCESS_POINTS = 106;
+    WIFI_APITEST = 107;
+    WIFI_INFO = 108;
+    WIFI_P2P = 109;
+    WIRELESS = 110;
+    QS_PANEL = 111;
+    QS_AIRPLANEMODE = 112;
+    QS_BLUETOOTH = 113;
+    QS_CAST = 114;
+    QS_CELLULAR = 115;
+    QS_COLORINVERSION = 116;
+    QS_DATAUSAGEDETAIL = 117;
+    QS_DND = 118;
+    QS_FLASHLIGHT = 119;
+    QS_HOTSPOT = 120;
+    QS_INTENT = 121;
+    QS_LOCATION = 122;
+    QS_ROTATIONLOCK = 123;
+    QS_USERDETAILITE = 124;
+    QS_USERDETAIL = 125;
+    QS_WIFI = 126;
+    NOTIFICATION_PANEL = 127;
+    NOTIFICATION_ITEM = 128;
+    NOTIFICATION_ITEM_ACTION = 129;
+    APPLICATIONS_ADVANCED = 130;
+    LOCATION_SCANNING = 131;
+    MANAGE_APPLICATIONS_ALL = 132;
+    MANAGE_APPLICATIONS_NOTIFICATIONS = 133;
+    ACTION_WIFI_ADD_NETWORK = 134;
+    ACTION_WIFI_CONNECT = 135;
+    ACTION_WIFI_FORCE_SCAN = 136;
+    ACTION_WIFI_FORGET = 137;
+    ACTION_WIFI_OFF = 138;
+    ACTION_WIFI_ON = 139;
+    MANAGE_PERMISSIONS = 140;
+    NOTIFICATION_ZEN_MODE_PRIORITY = 141;
+    NOTIFICATION_ZEN_MODE_AUTOMATION = 142;
+    MANAGE_DOMAIN_URLS = 143;
+    NOTIFICATION_ZEN_MODE_SCHEDULE_RULE = 144;
+    NOTIFICATION_ZEN_MODE_EXTERNAL_RULE = 145;
+    NOTIFICATION_ZEN_MODE_EVENT_RULE = 146;
+    ACTION_BAN_APP_NOTES = 147;
+    ACTION_DISMISS_ALL_NOTES = 148;
+    QS_DND_DETAILS = 149;
+    QS_BLUETOOTH_DETAILS = 150;
+    QS_CAST_DETAILS = 151;
+    QS_WIFI_DETAILS = 152;
+    QS_WIFI_TOGGLE = 153;
+    QS_BLUETOOTH_TOGGLE = 154;
+    QS_CELLULAR_TOGGLE = 155;
+    QS_SWITCH_USER = 156;
+    QS_CAST_SELECT = 157;
+    QS_CAST_DISCONNECT = 158;
+    ACTION_BLUETOOTH_TOGGLE = 159;
+    ACTION_BLUETOOTH_SCAN = 160;
+    ACTION_BLUETOOTH_RENAME = 161;
+    ACTION_BLUETOOTH_FILES = 162;
+    QS_DND_TIME = 163;
+    QS_DND_CONDITION_SELECT = 164;
+    QS_DND_ZEN_SELECT = 165;
+    QS_DND_TOGGLE = 166;
+    ACTION_ZEN_ALLOW_REMINDERS = 167;
+    ACTION_ZEN_ALLOW_EVENTS = 168;
+    ACTION_ZEN_ALLOW_MESSAGES = 169;
+    ACTION_ZEN_ALLOW_CALLS = 170;
+    ACTION_ZEN_ALLOW_REPEAT_CALLS = 171;
+    ACTION_ZEN_ADD_RULE = 172;
+    ACTION_ZEN_ADD_RULE_OK = 173;
+    ACTION_ZEN_DELETE_RULE = 174;
+    ACTION_ZEN_DELETE_RULE_OK = 175;
+    ACTION_ZEN_ENABLE_RULE = 176;
+    ACTION_AIRPLANE_TOGGLE = 177;
+    ACTION_CELL_DATA_TOGGLE = 178;
+    NOTIFICATION_ACCESS = 179;
+    NOTIFICATION_ZEN_MODE_ACCESS = 180;
+    APPLICATIONS_DEFAULT_APPS = 181;
+    APPLICATIONS_STORAGE_APPS = 182;
+    APPLICATIONS_USAGE_ACCESS_DETAIL = 183;
+    APPLICATIONS_HIGH_POWER_APPS = 184;
+    FUELGAUGE_HIGH_POWER_DETAILS = 185;
+    ACTION_LS_UNLOCK = 186;
+    ACTION_LS_SHADE = 187;
+    ACTION_LS_HINT = 188;
+    ACTION_LS_CAMERA = 189;
+    ACTION_LS_DIALER = 190;
+    ACTION_LS_LOCK = 191;
+    ACTION_LS_NOTE = 192;
+    ACTION_LS_QS = 193;
+    ACTION_SHADE_QS_PULL = 194;
+    ACTION_SHADE_QS_TAP = 195;
+    LOCKSCREEN = 196;
+    BOUNCER = 197;
+    SCREEN = 198;
+    NOTIFICATION_ALERT = 199;
+    ACTION_EMERGENCY_CALL = 200;
+    APPLICATIONS_MANAGE_ASSIST = 201;
+    PROCESS_STATS_SUMMARY = 202;
+    ACTION_ROTATION_LOCK = 203;
+    ACTION_NOTE_CONTROLS = 204;
+    ACTION_NOTE_INFO = 205;
+    ACTION_APP_NOTE_SETTINGS = 206;
+    VOLUME_DIALOG = 207;
+    VOLUME_DIALOG_DETAILS = 208;
+    ACTION_VOLUME_SLIDER = 209;
+    ACTION_VOLUME_STREAM = 210;
+    ACTION_VOLUME_KEY = 211;
+    ACTION_VOLUME_ICON = 212;
+    ACTION_RINGER_MODE = 213;
+    ACTION_ACTIVITY_CHOOSER_SHOWN = 214;
+    ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET = 215;
+    ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET = 216;
+    ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET = 217;
+    ACTION_BRIGHTNESS = 218;
+    ACTION_BRIGHTNESS_AUTO = 219;
+    BRIGHTNESS_DIALOG = 220;
+    SYSTEM_ALERT_WINDOW_APPS = 221;
+    DREAMING = 222;
+    DOZING = 223;
+    OVERVIEW_ACTIVITY = 224;
+    ABOUT_LEGAL_SETTINGS = 225;
+    ACTION_SEARCH_RESULTS = 226;
+    TUNER = 227;
+    TUNER_QS = 228;
+    TUNER_DEMO_MODE = 229;
+    TUNER_QS_REORDER = 230;
+    TUNER_QS_ADD = 231;
+    TUNER_QS_REMOVE = 232;
+    TUNER_STATUS_BAR_ENABLE = 233;
+    TUNER_STATUS_BAR_DISABLE = 234;
+    TUNER_DEMO_MODE_ENABLED = 235;
+    TUNER_DEMO_MODE_ON = 236;
+    TUNER_BATTERY_PERCENTAGE = 237;
+    FUELGAUGE_INACTIVE_APPS = 238;
+    ACTION_ASSIST_LONG_PRESS = 239;
+    FINGERPRINT_ENROLLING = 240;
+    FINGERPRINT_FIND_SENSOR = 241;
+    FINGERPRINT_ENROLL_FINISH = 242;
+    FINGERPRINT_ENROLL_INTRO = 243;
+    FINGERPRINT_ENROLL_ONBOARD = 244;
+    FINGERPRINT_ENROLL_SIDECAR = 245;
+    FINGERPRINT_ENROLLING_SETUP = 246;
+    FINGERPRINT_FIND_SENSOR_SETUP = 247;
+    FINGERPRINT_ENROLL_FINISH_SETUP = 248;
+    FINGERPRINT_ENROLL_INTRO_SETUP = 249;
+    FINGERPRINT_ENROLL_ONBOARD_SETUP = 250;
+    ACTION_FINGERPRINT_ENROLL = 251;
+    ACTION_FINGERPRINT_AUTH = 252;
+    ACTION_FINGERPRINT_DELETE = 253;
+    ACTION_FINGERPRINT_RENAME = 254;
+    ACTION_DOUBLE_TAP_POWER_CAMERA_GESTURE = 255;
+    ACTION_WIGGLE_CAMERA_GESTURE = 256;
+    QS_WORKMODE = 257;
+    BACKGROUND_CHECK_SUMMARY = 258;
+    QS_LOCK_TILE = 259;
+    QS_USER_TILE = 260;
+    QS_BATTERY_TILE = 261;
+    NOTIFICATION_ZEN_MODE_VISUAL_INTERRUPTIONS = 262;
+    ACTION_ZEN_ALLOW_PEEK = 263;
+    ACTION_ZEN_ALLOW_LIGHTS = 264;
+    NOTIFICATION_TOPIC_NOTIFICATION = 265;
+    ACTION_DEFAULT_SMS_APP_CHANGED = 266;
+    QS_COLOR_MATRIX = 267;
+    QS_CUSTOM = 268;
+    ACTION_ZEN_ALLOW_SCREEN_ON = 269;
+
+    // Logged when the user docks a window from recents by
+    // longpressing a task and dragging it to the dock area.
+    ACTION_WINDOW_DOCK_DRAG_DROP = 270;
+
+    // Logged when the user docks a fullscreen window by long pressing
+    // recents which also opens recents on the lower/right side.
+    ACTION_WINDOW_DOCK_LONGPRESS = 271;
+
+    // Logged when the user docks a window by dragging from the navbar
+    // which also opens recents on the lower/right side.
+    ACTION_WINDOW_DOCK_SWIPE = 272;
+
+    // Logged when the user launches a profile-specific app and we
+    // intercept it with the confirm credentials UI.
+    PROFILE_CHALLENGE = 273;
+
+    QS_BATTERY_DETAIL = 274;
+
+    // Logged when the user goes into the overview history.
+    OVERVIEW_HISTORY = 275;
+
+    // Logged when the user pages through overview.
+    ACTION_OVERVIEW_PAGE = 276;
+
+    // Logged when the user launches a task from overview.
+    ACTION_OVERVIEW_SELECT = 277;
+  }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9c24271..1434e5e 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -536,6 +536,7 @@
     // default actuion automatically.  Important for devices without direct input
     // devices.
     private boolean mShowDialogs = true;
+    private boolean mInVrMode = false;
 
     BroadcastQueue mFgBroadcastQueue;
     BroadcastQueue mBgBroadcastQueue;
@@ -2204,7 +2205,15 @@
             } break;
             case VR_MODE_CHANGE_MSG: {
                 VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
-                vrService.setVrMode(msg.arg1 != 0);
+                final boolean vrMode = msg.arg1 != 0;
+                vrService.setVrMode(vrMode);
+
+                if (mInVrMode != vrMode) {
+                    synchronized (ActivityManagerService.this) {
+                        mInVrMode = vrMode;
+                        mShowDialogs = shouldShowDialogs(mConfiguration, mInVrMode);
+                    }
+                }
             } break;
             }
         }
@@ -18439,7 +18448,7 @@
 
                 // TODO: If our config changes, should we auto dismiss any currently
                 // showing dialogs?
-                mShowDialogs = shouldShowDialogs(newConfig);
+                mShowDialogs = shouldShowDialogs(newConfig, mInVrMode);
 
                 AttributeCache ac = AttributeCache.instance();
                 if (ac != null) {
@@ -18528,13 +18537,13 @@
      * A thought: SystemUI might also want to get told about this, the Power
      * dialog / global actions also might want different behaviors.
      */
-    private static final boolean shouldShowDialogs(Configuration config) {
+    private static final boolean shouldShowDialogs(Configuration config, boolean inVrMode) {
         final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS
                                    && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH
                                    && config.navigation == Configuration.NAVIGATION_NONAV);
         final boolean uiIsNotCarType = !((config.uiMode & Configuration.UI_MODE_TYPE_MASK)
                                     == Configuration.UI_MODE_TYPE_CAR);
-        return inputMethodExists && uiIsNotCarType;
+        return inputMethodExists && uiIsNotCarType && !inVrMode;
     }
 
     @Override