Merge "Fix crash in IncidentCompanionService.  Null means *don't* do the check."
diff --git a/api/current.txt b/api/current.txt
index 1e6bf2c..8794780 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -23279,6 +23279,7 @@
     method @Deprecated public boolean registerRemoteController(android.media.RemoteController);
     method @Deprecated public int requestAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, int, int);
     method public int requestAudioFocus(@NonNull android.media.AudioFocusRequest);
+    method public void setAllowedCapturePolicy(int);
     method @Deprecated public void setBluetoothA2dpOn(boolean);
     method public void setBluetoothScoOn(boolean);
     method public void setMicrophoneMute(boolean);
@@ -52311,7 +52312,7 @@
     method public java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getActionList();
     method @Deprecated public int getActions();
     method public java.util.List<java.lang.String> getAvailableExtraData();
-    method public void getBoundsInParent(android.graphics.Rect);
+    method @Deprecated public void getBoundsInParent(android.graphics.Rect);
     method public void getBoundsInScreen(android.graphics.Rect);
     method public android.view.accessibility.AccessibilityNodeInfo getChild(int);
     method public int getChildCount();
@@ -52380,7 +52381,7 @@
     method public boolean removeChild(android.view.View, int);
     method public void setAccessibilityFocused(boolean);
     method public void setAvailableExtraData(java.util.List<java.lang.String>);
-    method public void setBoundsInParent(android.graphics.Rect);
+    method @Deprecated public void setBoundsInParent(android.graphics.Rect);
     method public void setBoundsInScreen(android.graphics.Rect);
     method public void setCanOpenPopup(boolean);
     method public void setCheckable(boolean);
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
index 685c067..b46c9e3 100644
--- a/cmds/incidentd/src/FdBuffer.cpp
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -31,7 +31,7 @@
 namespace incidentd {
 
 const ssize_t BUFFER_SIZE = 16 * 1024;  // 16 KB
-const ssize_t MAX_BUFFER_COUNT = 256;   // 4 MB max
+const ssize_t MAX_BUFFER_COUNT = 1536;   // 24 MB max
 
 FdBuffer::FdBuffer()
         :mBuffer(new EncodedBuffer(BUFFER_SIZE)),
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 385d491..774a359 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -1682,17 +1682,29 @@
     }
 
     /**
-     * Gets the node bounds in parent coordinates.
+     * Gets the node bounds in the viewParent's coordinates.
+     * {@link #getParent()} does not represent the source's viewParent.
+     * Instead it represents the result of {@link View#getParentForAccessibility()},
+     * which returns the closest ancestor where {@link View#isImportantForAccessibility()} is true.
+     * So this method is not reliable.
      *
      * @param outBounds The output node bounds.
+     * @deprecated Use {@link #getBoundsInScreen(Rect)} instead.
+     *
      */
+    @Deprecated
     public void getBoundsInParent(Rect outBounds) {
         outBounds.set(mBoundsInParent.left, mBoundsInParent.top,
                 mBoundsInParent.right, mBoundsInParent.bottom);
     }
 
     /**
-     * Sets the node bounds in parent coordinates.
+     * Sets the node bounds in the viewParent's coordinates.
+     * {@link #getParent()} does not represent the source's viewParent.
+     * Instead it represents the result of {@link View#getParentForAccessibility()},
+     * which returns the closest ancestor where {@link View#isImportantForAccessibility()} is true.
+     * So this method is not reliable.
+     *
      * <p>
      *   <strong>Note:</strong> Cannot be called from an
      *   {@link android.accessibilityservice.AccessibilityService}.
@@ -1702,7 +1714,9 @@
      * @param bounds The node bounds.
      *
      * @throws IllegalStateException If called from an AccessibilityService.
+     * @deprecated Accessibility services should not care about these bounds.
      */
+    @Deprecated
     public void setBoundsInParent(Rect bounds) {
         enforceNotSealed();
         mBoundsInParent.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 88713d1..a296d64 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2229,6 +2229,11 @@
     return AudioSystem::isHapticPlaybackSupported();
 }
 
+static jint
+android_media_AudioSystem_setAllowedCapturePolicy(JNIEnv *env, jobject thiz, jint uid, jint flags) {
+    return AudioSystem::setAllowedCapturePolicy(uid, flags);
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gMethods[] = {
@@ -2304,6 +2309,7 @@
     {"isHapticPlaybackSupported", "()Z", (void *)android_media_AudioSystem_isHapticPlaybackSupported},
     {"getHwOffloadEncodingFormatsSupportedForA2DP", "(Ljava/util/ArrayList;)I",
                     (void*)android_media_AudioSystem_getHwOffloadEncodingFormatsSupportedForA2DP},
+    {"setAllowedCapturePolicy", "(II)I", (void *)android_media_AudioSystem_setAllowedCapturePolicy},
 };
 
 static const JNINativeMethod gEventHandlerMethods[] = {
diff --git a/keystore/tests/Android.bp b/keystore/tests/Android.bp
new file mode 100644
index 0000000..e9b22c1
--- /dev/null
+++ b/keystore/tests/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2017 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.
+
+android_test {
+    name: "KeystoreTests",
+    // LOCAL_MODULE := keystore
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.rules",
+        "hamcrest-library",
+    ],
+    platform_apis: true,
+    libs: ["android.test.runner"],
+    certificate: "platform",
+}
diff --git a/location/Android.mk b/location/Android.mk
deleted file mode 100644
index 50509c6..0000000
--- a/location/Android.mk
+++ /dev/null
@@ -1,17 +0,0 @@
-# Copyright (C) 2010 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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(call all-subdir-makefiles, $(LOCAL_PATH))
\ No newline at end of file
diff --git a/location/tests/Android.bp b/location/tests/Android.bp
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/location/tests/Android.bp
@@ -0,0 +1 @@
+
diff --git a/location/tests/Android.mk b/location/tests/Android.mk
deleted file mode 100644
index 57848f3..0000000
--- a/location/tests/Android.mk
+++ /dev/null
@@ -1,3 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-
-include $(call all-makefiles-under, $(LOCAL_PATH))
\ No newline at end of file
diff --git a/location/tests/locationtests/Android.bp b/location/tests/locationtests/Android.bp
new file mode 100644
index 0000000..1a4e2c7
--- /dev/null
+++ b/location/tests/locationtests/Android.bp
@@ -0,0 +1,19 @@
+android_test {
+    name: "FrameworksLocationTests",
+    // Include all test java files.
+    srcs: ["src/**/*.java"],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    platform_apis: true,
+    static_libs: [
+        "androidx.test.rules",
+        "core-test-rules",
+        "guava",
+        "mockito-target-minus-junit4",
+        "frameworks-base-testutils",
+        "truth-prebuilt",
+    ],
+    test_suites: ["device-tests"],
+}
diff --git a/location/tests/locationtests/Android.mk b/location/tests/locationtests/Android.mk
deleted file mode 100644
index 3dcf694..0000000
--- a/location/tests/locationtests/Android.mk
+++ /dev/null
@@ -1,23 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-# We only want this apk build for tests.
-LOCAL_MODULE_TAGS := tests
-
-# Include all test java files.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
-LOCAL_PACKAGE_NAME := FrameworksLocationTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.test.rules \
-    core-test-rules \
-    guava \
-    mockito-target-minus-junit4 \
-    frameworks-base-testutils \
-    truth-prebuilt \
-
-LOCAL_COMPATIBILITY_SUITE := device-tests
-include $(BUILD_PACKAGE)
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index e925731..a3eee0a 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -710,20 +710,7 @@
          * @throws IllegalArgumentException if the argument is not a valid value.
          */
         public @NonNull Builder setAllowedCapturePolicy(@CapturePolicy int capturePolicy) {
-            switch (capturePolicy) {
-                case ALLOW_CAPTURE_BY_NONE:
-                    mFlags |= FLAG_NO_MEDIA_PROJECTION | FLAG_NO_SYSTEM_CAPTURE;
-                    break;
-                case ALLOW_CAPTURE_BY_SYSTEM:
-                    mFlags |= FLAG_NO_MEDIA_PROJECTION;
-                    mFlags &= ~FLAG_NO_SYSTEM_CAPTURE;
-                    break;
-                case ALLOW_CAPTURE_BY_ALL:
-                    mFlags &= ~FLAG_NO_SYSTEM_CAPTURE & ~FLAG_NO_MEDIA_PROJECTION;
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unknown allow playback capture policy");
-            }
+            mFlags = capturePolicyToFlags(capturePolicy, mFlags);
             return this;
         }
 
@@ -1219,6 +1206,24 @@
         }
     }
 
+    static int capturePolicyToFlags(@CapturePolicy int capturePolicy, int flags) {
+        switch (capturePolicy) {
+            case ALLOW_CAPTURE_BY_NONE:
+                flags |= FLAG_NO_MEDIA_PROJECTION | FLAG_NO_SYSTEM_CAPTURE;
+                break;
+            case ALLOW_CAPTURE_BY_SYSTEM:
+                flags |= FLAG_NO_MEDIA_PROJECTION;
+                flags &= ~FLAG_NO_SYSTEM_CAPTURE;
+                break;
+            case ALLOW_CAPTURE_BY_ALL:
+                flags &= ~FLAG_NO_SYSTEM_CAPTURE & ~FLAG_NO_MEDIA_PROJECTION;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown allow playback capture policy");
+        }
+        return flags;
+    }
+
     /** @hide */
     @IntDef({
         USAGE_UNKNOWN,
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 7cb5e00..dc5c663 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -1478,6 +1478,30 @@
         }
      }
 
+    /**
+     * Specifying if this audio may or may not be captured by other apps or the system.
+     *
+     * The default is {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}.
+     *
+     * Note that each audio track can also set its policy, in which case the most
+     * restrictive policy is always applied.
+     *
+     * @param capturePolicy one of
+     *     {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL},
+     *     {@link AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM},
+     *     {@link AudioAttributes#ALLOW_CAPTURE_BY_NONE}.
+     * @throws IllegalArgumentException if the argument is not a valid value.
+     */
+    public void setAllowedCapturePolicy(@AudioAttributes.CapturePolicy int capturePolicy) {
+        int flags = AudioAttributes.capturePolicyToFlags(capturePolicy, 0x0);
+        // TODO: got trough AudioService and save a cache to restore in case of AP crash
+        // TODO: also pass the package in case multiple packages have the same UID
+        int result = AudioSystem.setAllowedCapturePolicy(Process.myUid(), flags);
+        if (result != AudioSystem.AUDIO_STATUS_OK) {
+            Log.e(TAG, "Could not setAllowedCapturePolicy: " + result);
+        }
+    }
+
     //====================================================================
     // Offload query
     /**
diff --git a/media/java/android/media/AudioPlaybackCaptureConfiguration.java b/media/java/android/media/AudioPlaybackCaptureConfiguration.java
index 9ee6f87..bcaef03 100644
--- a/media/java/android/media/AudioPlaybackCaptureConfiguration.java
+++ b/media/java/android/media/AudioPlaybackCaptureConfiguration.java
@@ -29,13 +29,17 @@
  * Configuration for capturing audio played by other apps.
  *
  * Only the following audio can be captured:
- *  - usage MUST be UNKNOWN or GAME or MEDIA. All other usages CAN NOT be capturable.
- *  - audio attributes MUST NOT have the FLAG_NO_CAPTURE
+ *  - usage MUST be {@link AudioAttributes#USAGE_UNKNOWN} or {@link AudioAttributes#USAGE_GAME}
+ *    or {@link AudioAttributes#USAGE_MEDIA}. All other usages CAN NOT be captured.
+ *  - audio attributes MUST have its ${@link AudioAttributes.Builder#setAllowedCapturePolicy}
+ *    to {@link AudioAttributes#ALLOW_CAPTURE_BY_ALL}.
  *  - played by apps that MUST be in the same user profile as the capturing app
  *    (eg work profile can not capture user profile apps and vice-versa).
- *  - played by apps that MUST NOT have in their manifest.xml the application
- *    attribute android:allowAudioPlaybackCapture="false"
- *  - played by apps that MUST have a targetSdkVersion higher or equal to 29 (Q).
+ *  - played by apps for which the attribute allowAudioPlaybackCapture in their manifest
+ *    MUST either be:
+ *      * set to "true"
+ *      * not set, and their targetSdkVersion MUST be equal or higher to
+ *        {@link android.os.Build.VERSION_CODES#Q}.
  *
  * <p>An example for creating a capture configuration for capturing all media playback:
  *
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index a7760a80..2dd7f0f 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -25,6 +25,7 @@
 import android.annotation.TestApi;
 import android.annotation.UnsupportedAppUsage;
 import android.app.ActivityThread;
+import android.media.MediaRecorder.Source;
 import android.media.audiopolicy.AudioMix;
 import android.media.audiopolicy.AudioPolicy;
 import android.media.projection.MediaProjection;
@@ -539,7 +540,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        public Builder setAudioSource(int source) throws IllegalArgumentException {
+        public Builder setAudioSource(@Source int source) throws IllegalArgumentException {
             Preconditions.checkState(
                     mAudioPlaybackCaptureConfiguration == null,
                     ERROR_MESSAGE_SOURCE_MISMATCH);
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index ad255fe..d105fa3 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1022,6 +1022,11 @@
 
     public static native float getStreamVolumeDB(int stream, int index, int device);
 
+    /**
+     * @see AudioManager#setAllowedCapturePolicy()
+     */
+    public static native int setAllowedCapturePolicy(int uid, int flags);
+
     static boolean isOffloadSupported(@NonNull AudioFormat format, @NonNull AudioAttributes attr) {
         return native_is_offload_supported(format.getEncoding(), format.getSampleRate(),
                 format.getChannelMask(), format.getChannelIndexMask(),
diff --git a/media/java/android/media/HwAudioSource.java b/media/java/android/media/HwAudioSource.java
index e53b7e8..01a02f1 100644
--- a/media/java/android/media/HwAudioSource.java
+++ b/media/java/android/media/HwAudioSource.java
@@ -224,14 +224,4 @@
             return new HwAudioSource(mAudioDeviceInfo, mAudioAttributes);
         }
     }
-
-    /**
-     * Eliminate {@link #deprecateStreamTypeForPlayback(int, String, String)} in API list.
-     * TODO: remove this pseudo-override function
-     * @hide
-     */
-    public static void deprecateStreamTypeForPlayback(int streamType, String className,
-            String opName) throws IllegalArgumentException {
-        // Do nothing.
-    }
 }
diff --git a/media/java/android/media/MediaRecorder.java b/media/java/android/media/MediaRecorder.java
index 575a0bb..63b22df 100644
--- a/media/java/android/media/MediaRecorder.java
+++ b/media/java/android/media/MediaRecorder.java
@@ -18,6 +18,7 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.FloatRange;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -42,6 +43,8 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.RandomAccessFile;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
@@ -355,6 +358,22 @@
         public static final int HOTWORD = 1999;
     }
 
+    /** @hide */
+    @IntDef({
+        AudioSource.DEFAULT,
+        AudioSource.MIC,
+        AudioSource.VOICE_UPLINK,
+        AudioSource.VOICE_DOWNLINK,
+        AudioSource.VOICE_CALL,
+        AudioSource.CAMCORDER,
+        AudioSource.VOICE_RECOGNITION,
+        AudioSource.VOICE_COMMUNICATION,
+        AudioSource.UNPROCESSED,
+        AudioSource.VOICE_PERFORMANCE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Source {}
+
     // TODO make AudioSource static (API change) and move this method inside the AudioSource class
     /**
      * @hide
@@ -547,11 +566,11 @@
      * to be specified before setting recording-parameters or encoders. Call
      * this only before setOutputFormat().
      *
-     * @param audio_source the audio source to use
+     * @param audioSource the audio source to use
      * @throws IllegalStateException if it is called after setOutputFormat()
      * @see android.media.MediaRecorder.AudioSource
      */
-    public native void setAudioSource(int audio_source)
+    public native void setAudioSource(@Source int audioSource)
             throws IllegalStateException;
 
     /**
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
new file mode 100644
index 0000000..f0fbc50
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -0,0 +1,14 @@
+android_test {
+    name: "mediaframeworktest",
+    srcs: ["**/*.java"],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    static_libs: [
+        "mockito-target-minus-junit4",
+        "androidx.test.rules",
+        "android-ex-camera2",
+    ],
+    platform_apis: true,
+}
diff --git a/media/tests/MediaFrameworkTest/Android.mk b/media/tests/MediaFrameworkTest/Android.mk
deleted file mode 100644
index 167d255..0000000
--- a/media/tests/MediaFrameworkTest/Android.mk
+++ /dev/null
@@ -1,18 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    mockito-target-minus-junit4 \
-    androidx.test.rules \
-    android-ex-camera2
-
-LOCAL_PACKAGE_NAME := mediaframeworktest
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-include $(BUILD_PACKAGE)
diff --git a/media/tests/MtpTests/Android.bp b/media/tests/MtpTests/Android.bp
new file mode 100644
index 0000000..7d2c7c6
--- /dev/null
+++ b/media/tests/MtpTests/Android.bp
@@ -0,0 +1,6 @@
+android_test {
+    name: "MtpTests",
+    srcs: ["**/*.java"],
+    static_libs: ["androidx.test.rules"],
+    platform_apis: true,
+}
diff --git a/media/tests/MtpTests/Android.mk b/media/tests/MtpTests/Android.mk
deleted file mode 100644
index 4cee62e..0000000
--- a/media/tests/MtpTests/Android.mk
+++ /dev/null
@@ -1,13 +0,0 @@
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
-
-LOCAL_PACKAGE_NAME := MtpTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-include $(BUILD_PACKAGE)
diff --git a/packages/Shell/tests/Android.mk b/packages/Shell/tests/Android.mk
index b93ddde..44ff338 100644
--- a/packages/Shell/tests/Android.mk
+++ b/packages/Shell/tests/Android.mk
@@ -9,7 +9,7 @@
 LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test \
+    androidx.test.rules \
     mockito-target-minus-junit4 \
     ub-uiautomator \
     junit \
diff --git a/packages/Shell/tests/AndroidManifest.xml b/packages/Shell/tests/AndroidManifest.xml
index 6d564c6..e845ef9 100644
--- a/packages/Shell/tests/AndroidManifest.xml
+++ b/packages/Shell/tests/AndroidManifest.xml
@@ -36,7 +36,7 @@
         </activity>
     </application>
 
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.shell"
         android:label="Tests for Shell" />
 
diff --git a/packages/Shell/tests/AndroidTest.xml b/packages/Shell/tests/AndroidTest.xml
index e592d82..e03b68a 100644
--- a/packages/Shell/tests/AndroidTest.xml
+++ b/packages/Shell/tests/AndroidTest.xml
@@ -22,7 +22,7 @@
     <option name="test-tag" value="ShellTests" />
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.shell.tests" />
-        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
     </test>
 </configuration>
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java b/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java
index cef74ae..433eca2 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportProgressServiceTest.java
@@ -20,7 +20,6 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNull;
 
-import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.when;
 
@@ -29,12 +28,12 @@
 import android.content.Context;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
 import android.test.mock.MockContext;
 import android.util.Pair;
 
-import org.junit.Before;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
diff --git a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
index e69b0a8..3a71632 100644
--- a/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
+++ b/packages/Shell/tests/src/com/android/shell/BugreportReceiverTest.java
@@ -42,6 +42,40 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningServiceInfo;
+import android.app.Instrumentation;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.service.notification.StatusBarNotification;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.shell.ActionSendMultipleConsumerActivity.CustomActionSendMultipleListener;
+
+import libcore.io.Streams;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
 import java.io.BufferedOutputStream;
 import java.io.BufferedWriter;
 import java.io.ByteArrayOutputStream;
@@ -60,40 +94,6 @@
 import java.util.zip.ZipInputStream;
 import java.util.zip.ZipOutputStream;
 
-import libcore.io.Streams;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.junit.runner.RunWith;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningServiceInfo;
-import android.app.Instrumentation;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.service.notification.StatusBarNotification;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-
-import com.android.shell.ActionSendMultipleConsumerActivity.CustomActionSendMultipleListener;
-
 /**
  * Integration tests for {@link BugreportReceiver}.
  * <p>
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
index a4592d5..3c6dc73 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeDockHandler.java
@@ -87,7 +87,8 @@
     }
 
     private void requestPulseOutNow(State dozeState) {
-        if (dozeState == State.DOZE_REQUEST_PULSE || dozeState == State.DOZE_PULSING) {
+        if (dozeState == State.DOZE_REQUEST_PULSE || dozeState == State.DOZE_PULSING
+                || dozeState == State.DOZE_PULSING_BRIGHT) {
             final int pulseReason = mMachine.getPulseReason();
             if (pulseReason == DozeLog.PULSE_REASON_DOCKING) {
                 mDozeHost.stopPulsing();
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
index 36e28dc..0607654 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeFactory.java
@@ -71,7 +71,7 @@
                 new DozeScreenState(wrappedService, handler, params, wakeLock),
                 createDozeScreenBrightness(context, wrappedService, sensorManager, host, params,
                         handler),
-                new DozeWallpaperState(context, machine),
+                new DozeWallpaperState(context),
                 new DozeDockHandler(context, machine, host, config, handler, dockManager)
         });
 
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index bd7a421..3c9d4a9 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -33,7 +33,11 @@
     boolean isProvisioned();
     boolean isBlockingDoze();
 
-    void extendPulse();
+    /**
+     * Makes a current pulse last for twice as long.
+     * @param reason why we're extending it.
+     */
+    void extendPulse(int reason);
 
     void setAnimateWakeup(boolean animateWakeup);
     void setAnimateScreenOff(boolean animateScreenOff);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index c243899..f65112c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -59,6 +59,8 @@
         DOZE_REQUEST_PULSE,
         /** Pulse is showing. Device is awake and showing UI. */
         DOZE_PULSING,
+        /** Pulse is showing with bright wallpaper. Device is awake and showing UI. */
+        DOZE_PULSING_BRIGHT,
         /** Pulse is done showing. Followed by transition to DOZE or DOZE_AOD. */
         DOZE_PULSE_DONE,
         /** Doze is done. DozeService is finished. */
@@ -84,6 +86,7 @@
             switch (this) {
                 case DOZE_REQUEST_PULSE:
                 case DOZE_PULSING:
+                case DOZE_PULSING_BRIGHT:
                     return true;
                 default:
                     return false;
@@ -101,6 +104,7 @@
                 case DOZE:
                     return Display.STATE_OFF;
                 case DOZE_PULSING:
+                case DOZE_PULSING_BRIGHT:
                     return Display.STATE_ON;
                 case DOZE_AOD:
                 case DOZE_AOD_PAUSING:
@@ -202,6 +206,7 @@
         Assert.isMainThread();
         Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE
                 || mState == State.DOZE_PULSING
+                || mState == State.DOZE_PULSING_BRIGHT
                 || mState == State.DOZE_PULSE_DONE, "must be in pulsing state, but is " + mState);
         return mPulseReason;
     }
@@ -283,7 +288,8 @@
                     break;
                 case DOZE_PULSE_DONE:
                     Preconditions.checkState(
-                            mState == State.DOZE_REQUEST_PULSE || mState == State.DOZE_PULSING);
+                            mState == State.DOZE_REQUEST_PULSE || mState == State.DOZE_PULSING
+                                    || mState == State.DOZE_PULSING_BRIGHT);
                     break;
                 default:
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 7189a48..c9eb603 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -168,7 +168,7 @@
                 } else if (isPickup) {
                     gentleWakeUp(pulseReason);
                 } else {
-                    mDozeHost.extendPulse();
+                    mDozeHost.extendPulse(pulseReason);
                 }
             }, sensorPerformedProxCheck
                     || (mDockManager != null && mDockManager.isDocked()), pulseReason);
@@ -212,7 +212,8 @@
         final boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING);
         final boolean aod = (state == DozeMachine.State.DOZE_AOD);
 
-        if (state == DozeMachine.State.DOZE_PULSING) {
+        if (state == DozeMachine.State.DOZE_PULSING
+                || state == DozeMachine.State.DOZE_PULSING_BRIGHT) {
             boolean ignoreTouch = near;
             if (DEBUG) Log.i(TAG, "Prox changed, ignore touch = " + ignoreTouch);
             mDozeHost.onIgnoreTouchWhilePulsing(ignoreTouch);
@@ -276,6 +277,7 @@
                 mDozeSensors.setListening(false);
                 break;
             case DOZE_PULSING:
+            case DOZE_PULSING_BRIGHT:
                 mDozeSensors.setTouchscreenSensorsListening(false);
                 mDozeSensors.setProxListening(true);
                 break;
@@ -306,7 +308,16 @@
 
     private void requestPulse(final int reason, boolean performedProxCheck) {
         Assert.isMainThread();
-        mDozeHost.extendPulse();
+        mDozeHost.extendPulse(reason);
+
+        // When already pulsing we're allowed to show the wallpaper directly without
+        // requesting a new pulse.
+        if (mMachine.getState() == DozeMachine.State.DOZE_PULSING
+                && reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+            mMachine.requestState(DozeMachine.State.DOZE_PULSING_BRIGHT);
+            return;
+        }
+
         if (mPulsePending || !mAllowPulseTriggers || !canPulse()) {
             if (mAllowPulseTriggers) {
                 DozeLog.tracePulseDropped(mContext, mPulsePending, mMachine.getState(),
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 847182d..51e96d2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -94,7 +94,10 @@
                     @Override
                     public void onPulseStarted() {
                         try {
-                            mMachine.requestState(DozeMachine.State.DOZE_PULSING);
+                            mMachine.requestState(
+                                    reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN
+                                            ? DozeMachine.State.DOZE_PULSING_BRIGHT
+                                            : DozeMachine.State.DOZE_PULSING);
                         } catch (IllegalStateException e) {
                             // It's possible that the pulse was asynchronously cancelled while
                             // we were waiting for it to start (under stress conditions.)
@@ -148,6 +151,7 @@
         switch (state) {
             case DOZE_REQUEST_PULSE:
             case DOZE_PULSING:
+            case DOZE_PULSING_BRIGHT:
             case DOZE_PULSE_DONE:
                 mHost.setAnimateWakeup(true);
                 break;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index ca9f24f..1b3cd88 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -38,19 +38,17 @@
 
     private final IWallpaperManager mWallpaperManagerService;
     private final DozeParameters mDozeParameters;
-    private final DozeMachine mMachine;
     private boolean mIsAmbientMode;
 
-    public DozeWallpaperState(Context context, DozeMachine machine) {
-        this(machine, IWallpaperManager.Stub.asInterface(
+    public DozeWallpaperState(Context context) {
+        this(IWallpaperManager.Stub.asInterface(
                 ServiceManager.getService(Context.WALLPAPER_SERVICE)),
                 DozeParameters.getInstance(context));
     }
 
     @VisibleForTesting
-    DozeWallpaperState(DozeMachine machine, IWallpaperManager wallpaperManagerService,
+    DozeWallpaperState(IWallpaperManager wallpaperManagerService,
             DozeParameters parameters) {
-        mMachine = machine;
         mWallpaperManagerService = wallpaperManagerService;
         mDozeParameters = parameters;
     }
@@ -65,16 +63,13 @@
             case DOZE_AOD_PAUSED:
             case DOZE_REQUEST_PULSE:
             case DOZE_PULSE_DONE:
+            case DOZE_PULSING:
                 isAmbientMode = true;
                 break;
-            case DOZE_PULSING:
-                isAmbientMode =
-                        mMachine.getPulseReason() != DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN;
-                break;
+            case DOZE_PULSING_BRIGHT:
             default:
                 isAmbientMode = false;
         }
-
         final boolean animated;
         if (isAmbientMode) {
             animated = mDozeParameters.shouldControlScreenOff();
diff --git a/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt b/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt
index d7a2d9a..02ad0f1 100644
--- a/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt
@@ -17,7 +17,8 @@
     val timeRemainingMillis: Long,
     val severeThresholdMillis: Long,
     val lowThresholdMillis: Long,
-    val isBasedOnUsage: Boolean
+    val isBasedOnUsage: Boolean,
+    val isLowWarningEnabled: Boolean
 ) {
     /**
      * Returns whether hybrid warning logic/copy should be used for this snapshot
@@ -48,7 +49,8 @@
         NO_ESTIMATE_AVAILABLE.toLong(),
         NO_ESTIMATE_AVAILABLE.toLong(),
         NO_ESTIMATE_AVAILABLE.toLong(),
-        false
+        false,
+        true
     ) {
         this.isHybrid = false
     }
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
index bd130f4..a879227 100644
--- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
@@ -23,4 +23,9 @@
      * show a severe warning to the user.
      */
     long getSevereWarningThreshold();
+
+    /**
+     * Returns a boolean indicating if the low warning should be shown at all or not.
+     */
+    boolean getLowWarningEnabled();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
index 3f24176..bfb809e 100644
--- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
@@ -21,4 +21,9 @@
     public long getSevereWarningThreshold() {
         return 0;
     }
+
+    @Override
+    public boolean getLowWarningEnabled() {
+        return true;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 1863860..4e41108 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -284,7 +284,8 @@
                     plugged, bucket, mBatteryStatus, mLowBatteryReminderLevels[1],
                     mLowBatteryReminderLevels[0], estimate.getEstimateMillis(),
                     mEnhancedEstimates.getSevereWarningThreshold(),
-                    mEnhancedEstimates.getLowWarningThreshold(), estimate.isBasedOnUsage());
+                    mEnhancedEstimates.getLowWarningThreshold(), estimate.isBasedOnUsage(),
+                    mEnhancedEstimates.getLowWarningEnabled());
         } else {
             if (DEBUG) {
                 Slog.d(TAG, "using standard");
@@ -351,7 +352,6 @@
                 Slog.d(TAG, "Low warning marked as shown this cycle");
                 mLowWarningShownThisChargeCycle = true;
             }
-
         } else if (shouldDismissHybridWarning(currentSnapshot)) {
             if (DEBUG) {
                 Slog.d(TAG, "Dismissing warning");
@@ -375,8 +375,9 @@
             return false;
         }
 
-        // Only show the low warning once per charge cycle & no battery saver
-        final boolean canShowWarning = !mLowWarningShownThisChargeCycle && !snapshot.isPowerSaver()
+        // Only show the low warning if enabled once per charge cycle & no battery saver
+        final boolean canShowWarning = snapshot.isLowWarningEnabled()
+                && !mLowWarningShownThisChargeCycle && !snapshot.isPowerSaver()
                 && (snapshot.getTimeRemainingMillis() < snapshot.getLowThresholdMillis()
                 || snapshot.getBatteryLevel() <= snapshot.getLowLevelThreshold());
 
@@ -386,6 +387,7 @@
                 || snapshot.getBatteryLevel() <= snapshot.getSevereLevelThreshold());
 
         final boolean canShow = canShowWarning || canShowSevereWarning;
+
         if (DEBUG) {
             Slog.d(TAG, "Enhanced trigger is: " + canShow + "\nwith battery snapshot:"
                     + " mLowWarningShownThisChargeCycle: " + mLowWarningShownThisChargeCycle
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 494e6cd..ead39c69 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -439,6 +439,9 @@
         mSupportsRoundedCornersOnWindows = ScreenDecorationsUtils
                 .supportsRoundedCornersOnWindows(mContext.getResources());
 
+        // Assumes device always starts with back button until launcher tells it that it does not
+        mBackButtonAlpha = 1.0f;
+
         // Listen for the package update changes.
         if (mDeviceProvisionedController.getCurrentUser() == UserHandle.USER_SYSTEM) {
             updateEnabledState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 1949bad..0d2fe13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -92,7 +92,7 @@
     /**
      * Scrim opacity when the phone is about to wake-up.
      */
-    public static final float AOD2_SCRIM_ALPHA = 0.6f;
+    public static final float WAKE_SENSOR_SCRIM_ALPHA = 0.6f;
     /**
      * A scrim varies its opacity based on a busyness factor, for example
      * how many notifications are currently visible.
@@ -458,6 +458,23 @@
         mState.AOD.setAodFrontScrimAlpha(alpha);
     }
 
+    /**
+     * If the lock screen sensor is active.
+     */
+    public void setWakeLockScreenSensorActive(boolean active) {
+        for (ScrimState state : ScrimState.values()) {
+            state.setWakeLockScreenSensorActive(active);
+        }
+
+        if (mState == ScrimState.PULSING) {
+            float newBehindAlpha = mState.getBehindAlpha();
+            if (mCurrentBehindAlpha != newBehindAlpha) {
+                mCurrentBehindAlpha = newBehindAlpha;
+                updateScrims();
+            }
+        }
+    }
+
     protected void scheduleUpdate() {
         if (mUpdatePending) return;
 
@@ -904,10 +921,6 @@
         }
     }
 
-    public void setPulseReason(int pulseReason) {
-        ScrimState.PULSING.setPulseReason(pulseReason);
-    }
-
     public interface Callback {
         default void onStart() {
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 2f161d5..d152ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -19,7 +19,6 @@
 import android.graphics.Color;
 import android.os.Trace;
 
-import com.android.systemui.doze.DozeLog;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
 
@@ -129,16 +128,15 @@
         @Override
         public void prepare(ScrimState previousState) {
             mCurrentInFrontAlpha = 0f;
-            if (mPulseReason == DozeLog.PULSE_REASON_NOTIFICATION
-                    || mPulseReason == DozeLog.PULSE_REASON_DOCKING
-                    || mPulseReason == DozeLog.PULSE_REASON_INTENT) {
-                mCurrentBehindAlpha = previousState.getBehindAlpha();
-            } else {
-                mCurrentBehindAlpha = ScrimController.AOD2_SCRIM_ALPHA;
-            }
             mCurrentBehindTint = Color.BLACK;
             mBlankScreen = mDisplayRequiresBlanking;
         }
+
+        @Override
+        public float getBehindAlpha() {
+            return mWakeLockScreenSensorActive ? ScrimController.WAKE_SENSOR_SCRIM_ALPHA
+                    : AOD.getBehindAlpha();
+        }
     },
 
     /**
@@ -199,7 +197,7 @@
     int mIndex;
     boolean mHasBackdrop;
     boolean mLaunchingAffordanceWithPreview;
-    int mPulseReason;
+    boolean mWakeLockScreenSensorActive;
 
     ScrimState(int index) {
         mIndex = index;
@@ -264,10 +262,6 @@
         mAodFrontScrimAlpha = aodFrontScrimAlpha;
     }
 
-    public void setPulseReason(int pulseReason) {
-        mPulseReason = pulseReason;
-    }
-
     public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
     }
@@ -287,4 +281,8 @@
     public void setHasBackdrop(boolean hasBackdrop) {
         mHasBackdrop = hasBackdrop;
     }
+
+    public void setWakeLockScreenSensorActive(boolean active) {
+        mWakeLockScreenSensorActive = active;
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index d7f070f..db91d01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -3908,7 +3908,6 @@
 
         @Override
         public void pulseWhileDozing(@NonNull PulseCallback callback, int reason) {
-            mScrimController.setPulseReason(reason);
             if (reason == DozeLog.PULSE_REASON_SENSOR_LONG_PRESS) {
                 mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_GESTURE,
                         "com.android.systemui:LONG_PRESS");
@@ -3916,6 +3915,10 @@
                 return;
             }
 
+            if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+                mScrimController.setWakeLockScreenSensorActive(true);
+            }
+
             boolean passiveAuthInterrupt = reason == DozeLog.PULSE_REASON_NOTIFICATION;
             // Set the state to pulsing, so ScrimController will know what to do once we ask it to
             // execute the transition. The pulse callback will then be invoked when the scrims
@@ -3934,6 +3937,7 @@
                     mPulsing = false;
                     callback.onPulseFinished();
                     updateNotificationPanelTouchState();
+                    mScrimController.setWakeLockScreenSensorActive(false);
                     setPulsing(false);
                 }
 
@@ -4010,7 +4014,10 @@
         }
 
         @Override
-        public void extendPulse() {
+        public void extendPulse(int reason) {
+            if (reason == DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN) {
+                mScrimController.setWakeLockScreenSensorActive(true);
+            }
             if (mDozeScrimController.isPulsing() && mAmbientPulseManager.hasNotifications()) {
                 mAmbientPulseManager.extendPulse();
             } else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index cbaabf7..3b32d95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -760,9 +760,10 @@
     }
 
     public boolean bouncerNeedsScrimming() {
-        return mOccluded || mBouncer.willDismissWithAction()  || mBouncer.needsFullscreenBouncer()
+        return mOccluded || mBouncer.willDismissWithAction()
                 || mStatusBar.isFullScreenUserSwitcherState()
-                || (mBouncer.isShowing() && mBouncer.isScrimmed());
+                || (mBouncer.isShowing() && mBouncer.isScrimmed())
+                || mBouncer.isFullscreenBouncer();
     }
 
     public void dump(PrintWriter pw) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
index dc42872..abfa755 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeHostFake.java
@@ -86,7 +86,7 @@
     }
 
     @Override
-    public void extendPulse() {
+    public void extendPulse(int reason) {
         pulseExtended = true;
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
index beba905..87ae85f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeWallpaperStateTest.java
@@ -45,12 +45,11 @@
     private DozeWallpaperState mDozeWallpaperState;
     @Mock IWallpaperManager mIWallpaperManager;
     @Mock DozeParameters mDozeParameters;
-    @Mock DozeMachine mMachine;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        mDozeWallpaperState = new DozeWallpaperState(mMachine, mIWallpaperManager, mDozeParameters);
+        mDozeWallpaperState = new DozeWallpaperState(mIWallpaperManager, mDozeParameters);
     }
 
     @Test
@@ -110,28 +109,18 @@
     }
 
     @Test
-    public void testTransitionTo_notificationPulseIsAmbientMode() throws RemoteException {
-        when(mMachine.getPulseReason()).thenReturn(DozeLog.PULSE_REASON_NOTIFICATION);
-        mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE,
-                DozeMachine.State.DOZE_PULSING);
-        verify(mIWallpaperManager).setInAmbientMode(eq(true), eq(0L));
-    }
-
-    @Test
     public void testTransitionTo_wakeFromPulseIsNotAmbientMode() throws RemoteException {
-        when(mMachine.getPulseReason()).thenReturn(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN);
         mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_AOD,
                 DozeMachine.State.DOZE_REQUEST_PULSE);
         reset(mIWallpaperManager);
 
         mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE,
-                DozeMachine.State.DOZE_PULSING);
+                DozeMachine.State.DOZE_PULSING_BRIGHT);
         verify(mIWallpaperManager).setInAmbientMode(eq(false), anyLong());
     }
 
     @Test
     public void testTransitionTo_animatesWhenWakingUpFromPulse() throws RemoteException {
-        when(mMachine.getPulseReason()).thenReturn(DozeLog.PULSE_REASON_NOTIFICATION);
         mDozeWallpaperState.transitionTo(DozeMachine.State.DOZE_REQUEST_PULSE,
                 DozeMachine.State.DOZE_PULSING);
         reset(mIWallpaperManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index f51e473..5928a07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -265,6 +265,12 @@
         state.mIsPowerSaver = true;
         shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
         assertThat(shouldShow).isFalse();
+
+        state.mIsPowerSaver = false;
+        // if disabled we should not show the low warning.
+        state.mIsLowLevelWarningEnabled = false;
+        shouldShow = mPowerUI.shouldShowHybridWarning(state.get());
+        assertThat(shouldShow).isFalse();
     }
 
     @Test
@@ -365,7 +371,7 @@
         assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_HYBRID_THRESHOLD);
         BatteryStateSnapshot snapshot = new BatteryStateSnapshot(
                 BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD,
-                0, 0, -1, 0, 0, false);
+                0, 0, -1, 0, 0, false, true);
         mPowerUI.mLastBatteryStateSnapshot = snapshot;
 
         // query again since the estimate was -1
@@ -375,7 +381,7 @@
         assertThat(refreshedEstimate.getEstimateMillis()).isEqualTo(BELOW_SEVERE_HYBRID_THRESHOLD);
         snapshot = new BatteryStateSnapshot(
                 BATTERY_LEVEL_10, false, false, 0, BatteryManager.BATTERY_HEALTH_GOOD, 0,
-                0, BELOW_SEVERE_HYBRID_THRESHOLD, 0, 0, false);
+                0, BELOW_SEVERE_HYBRID_THRESHOLD, 0, 0, false, true);
         mPowerUI.mLastBatteryStateSnapshot = snapshot;
 
         // Battery level hasn't changed, so we don't query again
@@ -536,13 +542,14 @@
         public long mTimeRemainingMillis = Duration.ofHours(24).toMillis();
         public boolean mIsBasedOnUsage = true;
         public boolean mIsHybrid = true;
+        public boolean mIsLowLevelWarningEnabled = true;
 
         public BatteryStateSnapshot get() {
             if (mIsHybrid) {
                 return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket,
                         mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold,
                         mTimeRemainingMillis, mSevereThresholdMillis, mLowThresholdMillis,
-                        mIsBasedOnUsage);
+                        mIsBasedOnUsage, mIsLowLevelWarningEnabled);
             } else {
                 return new BatteryStateSnapshot(mBatteryLevel, mIsPowerSaver, mPlugged, mBucket,
                         mBatteryStatus, mSevereLevelThreshold, mLowLevelThreshold);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 539851f..191c983 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -46,7 +46,6 @@
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.util.function.TriConsumer;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.doze.DozeLog;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.util.wakelock.WakeLock;
 import com.android.systemui.utils.os.FakeHandler;
@@ -140,7 +139,6 @@
         assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
 
         // Pulsing notification should conserve AOD wallpaper.
-        mScrimController.setPulseReason(DozeLog.PULSE_REASON_NOTIFICATION);
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
         assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_TRANSPARENT);
@@ -225,14 +223,17 @@
         mScrimController.finishAnimationsImmediately();
         assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
 
-        mScrimController.setPulseReason(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN);
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
         // Front scrim should be transparent
         // Back scrim should be semi-transparent so the user can see the wallpaper
         // Pulse callback should have been invoked
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
         assertScrimTint(mScrimBehind, true /* tinted */);
+
+        mScrimController.setWakeLockScreenSensorActive(true);
+        mScrimController.finishAnimationsImmediately();
+        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_SEMI_TRANSPARENT);
     }
 
     @Test
@@ -486,7 +487,6 @@
     @Test
     public void testHoldsPulsingWallpaperAnimationLock() {
         // Pre-conditions
-        mScrimController.setPulseReason(DozeLog.PULSE_REASON_NOTIFICATION);
         mScrimController.transitionTo(ScrimState.PULSING);
         mScrimController.finishAnimationsImmediately();
         reset(mWakeLock);
@@ -508,30 +508,6 @@
     }
 
     @Test
-    public void testWillHidePulsingWallpaper_whenNotification() {
-        mScrimController.setWallpaperSupportsAmbientMode(false);
-        mScrimController.transitionTo(ScrimState.AOD);
-        mScrimController.finishAnimationsImmediately();
-        mScrimController.setPulseReason(DozeLog.PULSE_REASON_NOTIFICATION);
-        mScrimController.transitionTo(ScrimState.PULSING);
-        mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true);
-    }
-
-    @Test
-    public void testWillHidePulsingWallpaper_whenDocking() {
-        mScrimController.setWallpaperSupportsAmbientMode(false);
-        mScrimController.transitionTo(ScrimState.AOD);
-        mScrimController.finishAnimationsImmediately();
-        mScrimController.setPulseReason(DozeLog.PULSE_REASON_DOCKING);
-        mScrimController.transitionTo(ScrimState.PULSING);
-        mScrimController.finishAnimationsImmediately();
-        assertScrimVisibility(VISIBILITY_FULLY_TRANSPARENT, VISIBILITY_FULLY_OPAQUE);
-        assertScrimTint(mScrimBehind, true);
-    }
-
-    @Test
     public void testConservesExpansionOpacityAfterTransition() {
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         mScrimController.setPanelExpansion(0.5f);
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 39f7f0f..6a9f5b6 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -357,10 +357,27 @@
                 && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel);
     }
 
+    private boolean shouldShutdownLocked() {
+        if (mHealthInfo.batteryLevel > 0) {
+            return false;
+        }
+
+        // Battery-less devices should not shutdown.
+        if (!mHealthInfo.batteryPresent) {
+            return false;
+        }
+
+        // If battery state is not CHARGING, shutdown.
+        // - If battery present and state == unknown, this is an unexpected error state.
+        // - If level <= 0 and state == full, this is also an unexpected state
+        // - All other states (NOT_CHARGING, DISCHARGING) means it is not charging.
+        return mHealthInfo.batteryStatus != BatteryManager.BATTERY_STATUS_CHARGING;
+    }
+
     private void shutdownIfNoPowerLocked() {
         // shut down gracefully if our battery is critically low and we are not powered.
         // wait until the system has booted before attempting to display the shutdown dialog.
-        if (mHealthInfo.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) {
+        if (shouldShutdownLocked()) {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 8b10267..73e0439 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -627,11 +627,6 @@
                 r.callingPackage = callingPackage;
                 r.callerUid = Binder.getCallingUid();
                 r.callerPid = Binder.getCallingPid();
-                if (r.subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID && r.subId != subId) {
-                    throw new IllegalArgumentException(
-                            "PhoneStateListener cannot concurrently listen on multiple " +
-                                    "subscriptions. Previously registered on subId: " + r.subId);
-                }
                 // Legacy applications pass SubscriptionManager.DEFAULT_SUB_ID,
                 // force all illegal subId to SubscriptionManager.DEFAULT_SUB_ID
                 if (!SubscriptionManager.isValidSubscriptionId(subId)) {
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index b2c7ff3..87b272b 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -69,6 +69,9 @@
                                                 AudioEffect.Descriptor[] effects,
                                                 int activeSource, String packName) {
         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
+            // still want to log event, it just won't appear in recording configurations
+            sEventLogger.log(new RecordingEvent(event, uid, session, source, packName)
+                    .printLog(TAG));
             return;
         }
         String clientEffectName =  clientEffects.length == 0 ? "None" : clientEffects[0].name;
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 10f7db5..a2882de 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -961,7 +961,10 @@
     @ServiceThreadOnly
     void doManualPortSwitching(int portId, IHdmiControlCallback callback) {
         assertRunOnServiceThread();
-        // TODO: validate port ID
+        if (!mService.isValidPortId(portId)) {
+            invokeCallback(callback, HdmiControlManager.RESULT_TARGET_NOT_AVAILABLE);
+            return;
+        }
         if (portId == getLocalActivePort()) {
             invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
             return;
diff --git a/startop/iorap/tests/Android.bp b/startop/iorap/tests/Android.bp
index 4359978..3e60ad4 100644
--- a/startop/iorap/tests/Android.bp
+++ b/startop/iorap/tests/Android.bp
@@ -16,32 +16,48 @@
 java_library {
     name: "libiorap-java-test-lib",
     srcs: ["src/**/*.kt"],
-
     static_libs: [
-      // Non-test dependencies
-
-      // library under test
-      "services.startop.iorap",
-      // need the system_server code to be on the classpath,
-      "services.core",
-
-      // Test Dependencies
-
-      // test android dependencies
-      "platform-test-annotations",
-      "androidx.test.rules",
-      // test framework dependencies
-      "mockito-target-inline-minus-junit4",
-      // "mockito-target-minus-junit4",
+        // Non-test dependencies
+        // library under test
+        "services.startop.iorap",
+        // need the system_server code to be on the classpath,
+        "services.core",
+        // Test Dependencies
+        // test android dependencies
+        "platform-test-annotations",
+        "androidx.test.rules",
+        // test framework dependencies
+        "mockito-target-inline-minus-junit4",
+        // "mockito-target-minus-junit4",
         // Mockito also requires JNI (see Android.mk)
         // and android:debuggable=true (see AndroidManifest.xml)
-      "truth-prebuilt",
+        "truth-prebuilt",
     ],
-
     // sdk_version: "current",
     // certificate: "platform",
-
-    libs: ["android.test.base", "android.test.runner"],
-
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
     // test_suites: ["device-tests"],
 }
+
+android_test {
+    name: "libiorap-java-tests",
+    dxflags: ["--multi-dex"],
+    test_suites: ["device-tests"],
+    static_libs: ["libiorap-java-test-lib"],
+    compile_multilib: "both",
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+        "libmultiplejvmtiagentsinterferenceagent",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+    // Use private APIs
+    certificate: "platform",
+    platform_apis: true,
+}
diff --git a/startop/iorap/tests/Android.mk b/startop/iorap/tests/Android.mk
deleted file mode 100644
index fa8c8b5..0000000
--- a/startop/iorap/tests/Android.mk
+++ /dev/null
@@ -1,49 +0,0 @@
-# Copyright (C) 2018 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.
-
-# android_test does not support JNI libraries
-# TODO: once b/80095087 is fixed, rewrite this back to android_test
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_JACK_FLAGS := --multi-dex native
-LOCAL_DX_FLAGS := --multi-dex
-
-LOCAL_PACKAGE_NAME := libiorap-java-tests
-LOCAL_COMPATIBILITY_SUITE := device-tests
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    libiorap-java-test-lib
-
-LOCAL_MULTILIB := both
-
-LOCAL_JNI_SHARED_LIBRARIES := \
-    libdexmakerjvmtiagent \
-    libstaticjvmtiagent \
-    libmultiplejvmtiagentsinterferenceagent
-
-LOCAL_JAVA_LIBRARIES := \
-    android.test.base \
-    android.test.runner
-
-# Use private APIs
-LOCAL_CERTIFICATE := platform
-LOCAL_PRIVATE_PLATFORM_APIS := true
-
-# Disable presubmit test until it works with disabled iorap by default.
-LOCAL_PRESUBMIT_DISABLED := true
-
-include $(BUILD_PACKAGE)
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ffd5b16..e4debd6 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4845,22 +4845,18 @@
      * Registers a listener object to receive notification of changes
      * in specified telephony states.
      * <p>
-     * To register a listener, pass a {@link PhoneStateListener} and specify at least one telephony
-     * state of interest in the events argument.
+     * To register a listener, pass a {@link PhoneStateListener}
+     * and specify at least one telephony state of interest in
+     * the events argument.
      *
-     * At registration, and when a specified telephony state changes, the telephony manager invokes
-     * the appropriate callback method on the listener object and passes the current (updated)
-     * values.
+     * At registration, and when a specified telephony state
+     * changes, the telephony manager invokes the appropriate
+     * callback method on the listener object and passes the
+     * current (updated) values.
      * <p>
-     * To un-register a listener, pass the listener object and set the events argument to
+     * To unregister a listener, pass the listener object and set the
+     * events argument to
      * {@link PhoneStateListener#LISTEN_NONE LISTEN_NONE} (0).
-     *
-     * If this TelephonyManager object has been created with {@link #createForSubscriptionId},
-     * applies to the given subId. Otherwise, applies to
-     * {@link SubscriptionManager#getDefaultSubscriptionId()}. To listen events for multiple subIds,
-     * pass a separate listener object to each TelephonyManager object created with
-     * {@link #createForSubscriptionId}.
-     *
      * Note: if you call this method while in the middle of a binder transaction, you <b>must</b>
      * call {@link android.os.Binder#clearCallingIdentity()} before calling this method. A
      * {@link SecurityException} will be thrown otherwise.
@@ -4875,18 +4871,17 @@
         if (mContext == null) return;
         try {
             boolean notifyNow = (getITelephony() != null);
+            // If the listener has not explicitly set the subId (for example, created with the
+            // default constructor), replace the subId so it will listen to the account the
+            // telephony manager is created with.
+            if (listener.mSubId == null) {
+                listener.mSubId = mSubId;
+            }
+
             ITelephonyRegistry registry = getTelephonyRegistry();
             if (registry != null) {
-                // listen to the subId the telephony manager is created with. Ignore subId in
-                // PhoneStateListener.
-                registry.listenForSubscriber(mSubId, getOpPackageName(),
+                registry.listenForSubscriber(listener.mSubId, getOpPackageName(),
                         listener.callback, events, notifyNow);
-                // TODO: remove this once we remove PhoneStateListener constructor with subId.
-                if (events == PhoneStateListener.LISTEN_NONE) {
-                    listener.mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-                } else {
-                    listener.mSubId = mSubId;
-                }
             } else {
                 Rlog.w(TAG, "telephony registry not ready.");
             }
@@ -10703,6 +10698,25 @@
     }
 
     /**
+     * It indicates whether modem is enabled or not per slot.
+     * It's the corresponding status of {@link #enableModemForSlot}.
+     *
+     * @param slotIndex which slot it's checking.
+     * @hide
+     */
+    public boolean isModemEnabledForSlot(int slotIndex) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.isModemEnabledForSlot(slotIndex, mContext.getOpPackageName());
+            }
+        } catch (RemoteException ex) {
+            Log.e(TAG, "enableModem RemoteException", ex);
+        }
+        return false;
+    }
+
+    /**
      * Broadcast intent action for network country code changes.
      *
      * <p>
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9e2d9ee..8332ffe 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1958,5 +1958,7 @@
     /**
      * Get the IRadio HAL Version encoded as 100 * MAJOR_VERSION + MINOR_VERSION or -1 if unknown
      */
-     int getRadioHalVersion();
+    int getRadioHalVersion();
+
+    boolean isModemEnabledForSlot(int slotIndex, String callingPackage);
 }
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index 41cb74a..e36f976 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -17,6 +17,7 @@
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="com.android.server.wm.flicker"/>
+        <option name="exclude-annotation" value="org.junit.Ignore" />
         <option name="shell-timeout" value="6600s" />
         <option name="test-timeout" value="6000s" />
         <option name="hidden-api-checks" value="false" />
diff --git a/keystore/tests/Android.mk b/tests/GamePerformance/Android.mk
similarity index 68%
rename from keystore/tests/Android.mk
rename to tests/GamePerformance/Android.mk
index 99d3197..58654de 100644
--- a/keystore/tests/Android.mk
+++ b/tests/GamePerformance/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2017 The Android Open Source Project
+# Copyright (C) 2018 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.
@@ -12,23 +12,28 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-LOCAL_PATH := $(call my-dir)
+LOCAL_PATH:= $(call my-dir)
+
 include $(CLEAR_VARS)
 
-# LOCAL_MODULE := keystore
+# Don't include this package in any target
 LOCAL_MODULE_TAGS := tests
 
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PROGUARD_ENABLED := disabled
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.test.rules hamcrest-library
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 
-LOCAL_PACKAGE_NAME := KeystoreTests
+LOCAL_JAVA_LIBRARIES := android.test.base android.test.runner
+
+LOCAL_PACKAGE_NAME := GamePerformance
+
 LOCAL_PRIVATE_PLATFORM_APIS := true
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
-
 LOCAL_CERTIFICATE := platform
 
-include $(BUILD_PACKAGE)
 
+include $(BUILD_PACKAGE)
diff --git a/tests/GamePerformance/AndroidManifest.xml b/tests/GamePerformance/AndroidManifest.xml
new file mode 100644
index 0000000..b331e2c
--- /dev/null
+++ b/tests/GamePerformance/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.gameperformance">
+    <uses-sdk android:minSdkVersion="25"/>
+    <uses-feature android:glEsVersion="0x00020000" android:required="true" />
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <application android:theme="@style/noeffects">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.gameperformance.GamePerformanceActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+                     android:targetPackage="android.gameperformance">
+    </instrumentation>
+</manifest>
diff --git a/tests/GamePerformance/res/values/themes.xml b/tests/GamePerformance/res/values/themes.xml
new file mode 100644
index 0000000..6313071
--- /dev/null
+++ b/tests/GamePerformance/res/values/themes.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2018 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.
+ -->
+<resources>
+    <style name="noeffects" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:fadingEdge">none</item>
+        <item name="android:windowContentTransitions">false</item>
+        <item name="android:windowAnimationStyle">@null</item>
+    </style>
+</resources>
diff --git a/tests/GamePerformance/src/android/gameperformance/ATraceRunner.java b/tests/GamePerformance/src/android/gameperformance/ATraceRunner.java
new file mode 100644
index 0000000..25754fd
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/ATraceRunner.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2018 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.gameperformance;
+
+import java.io.BufferedReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import android.app.Instrumentation;
+import android.os.AsyncTask;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+/**
+ * Helper that runs atrace command for required duration and category. Results are read from
+ * the output of atrace and serialized to the provided file. We cannot use direct atrace to
+ * file because atrace is executed in UI automator context and analysis is done in test context.
+ * In last case output file is not accessible from the both contexts.
+ */
+public class ATraceRunner extends AsyncTask<Void, Integer, Boolean>{
+    private final static String TAG = "ATraceRunner";
+
+    // Report that atrace is done.
+    public interface Delegate {
+        public void onProcessed(boolean success);
+    }
+
+    private final Instrumentation mInstrumentation;
+    private final String mOutput;
+    private final int mTimeInSeconds;
+    private final String mCategory;
+    private final Delegate mDelegate;
+
+    public ATraceRunner(Instrumentation instrumentation,
+                        String output,
+                        int timeInSeconds,
+                        String category,
+                        Delegate delegate) {
+        mInstrumentation = instrumentation;
+        mOutput = output;
+        mTimeInSeconds = timeInSeconds;
+        mCategory = category;
+        mDelegate = delegate;
+    }
+
+    @Override
+    protected Boolean doInBackground(Void... params) {
+        BufferedReader bufferedReader = null;
+        FileWriter writer = null;
+        try {
+            // Run the command.
+            final String cmd = "atrace -t " + mTimeInSeconds + " " + mCategory;
+            Log.i(TAG, "Running atrace... " + cmd);
+            writer = new FileWriter(mOutput);
+            final ParcelFileDescriptor fd =
+                    mInstrumentation.getUiAutomation().executeShellCommand(cmd);
+            bufferedReader = new BufferedReader(
+                    new InputStreamReader(new ParcelFileDescriptor.AutoCloseInputStream(fd)));
+            String line;
+            while ((line = bufferedReader.readLine()) != null) {
+                writer.write(line);
+                writer.write("\n");
+            }
+            Log.i(TAG, "Running atrace... DONE");
+            return true;
+        } catch (IOException e) {
+            Log.i(TAG, "atrace failed", e);
+            return false;
+        } finally {
+            Utils.closeQuietly(bufferedReader);
+            Utils.closeQuietly(writer);
+        }
+    }
+
+    @Override
+    protected void onPostExecute(Boolean result) {
+        mDelegate.onProcessed(result);
+    }
+
+}
diff --git a/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java b/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java
new file mode 100644
index 0000000..2b37280
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/CustomOpenGLView.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2018 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.gameperformance;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.opengles.GL10;
+
+import android.content.Context;
+import android.opengl.GLES20;
+import android.opengl.GLSurfaceView;
+
+public class CustomOpenGLView extends GLSurfaceView {
+    private Random mRandom;
+    private List<Long> mFrameTimes;
+
+    public CustomOpenGLView(Context context) {
+        super(context);
+
+        mRandom = new Random();
+        mFrameTimes = new ArrayList<Long>();
+
+        setEGLContextClientVersion(2);
+
+        setRenderer(new GLSurfaceView.Renderer() {
+            @Override
+            public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+                GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
+                gl.glClearDepthf(1.0f);
+                gl.glEnable(GL10.GL_DEPTH_TEST);
+                gl.glDepthFunc(GL10.GL_LEQUAL);
+
+                gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT,
+                          GL10.GL_NICEST);            }
+
+            @Override
+            public void onSurfaceChanged(GL10 gl, int width, int height) {
+                GLES20.glViewport(0, 0, width, height);
+            }
+
+            @Override
+            public void onDrawFrame(GL10 gl) {
+                GLES20.glClearColor(
+                        mRandom.nextFloat(), mRandom.nextFloat(), mRandom.nextFloat(), 1.0f);
+                gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+                synchronized (mFrameTimes) {
+                    mFrameTimes.add(System.currentTimeMillis());
+                }
+            }
+        });
+        setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
+    }
+
+    /**
+     * Resets frame times in order to calculate fps for different test pass.
+     */
+    public void resetFrameTimes() {
+        synchronized (mFrameTimes) {
+            mFrameTimes.clear();
+        }
+    }
+
+    /**
+     * Returns current fps based on collected frame times.
+     */
+    public double getFps() {
+        synchronized (mFrameTimes) {
+            if (mFrameTimes.size() < 2) {
+                return 0.0f;
+            }
+            return 1000.0 * mFrameTimes.size() /
+                    (mFrameTimes.get(mFrameTimes.size() - 1) - mFrameTimes.get(0));
+        }
+    }
+}
diff --git a/tests/GamePerformance/src/android/gameperformance/CustomSurfaceView.java b/tests/GamePerformance/src/android/gameperformance/CustomSurfaceView.java
new file mode 100644
index 0000000..5616136
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/CustomSurfaceView.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2018 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.gameperformance;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.PixelFormat;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Trace;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+
+/**
+ * Minimal SurfaceView that sends buffer on request.
+ */
+public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
+    // Tag for trace when buffer is requested.
+    public final static String LOCAL_REQUEST_BUFFER = "localRequestBuffer";
+    // Tag for trace when buffer is posted.
+    public final static String LOCAL_POST_BUFFER = "localPostBuffer";
+
+    private final Object mSurfaceLock = new Object();
+    // Keeps frame times. Used to calculate fps.
+    private List<Long> mFrameTimes;
+    // Surface to send.
+    private Surface mSurface;
+    private Handler mHandler;
+
+    private Runnable mInvalidateSurfaceTask = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (mSurfaceLock) {
+                if (mSurface == null) {
+                    return;
+                }
+                invalidateSurface(true, true);
+                mHandler.post(this);
+            }
+        }
+    };
+
+    public CustomSurfaceView(Context context) {
+        super(context);
+        mFrameTimes = new ArrayList<Long>();
+        getHolder().addCallback(this);
+        getHolder().setFormat(PixelFormat.OPAQUE);
+
+        HandlerThread thread = new HandlerThread("SurfaceInvalidator");
+        thread.start();
+        mHandler = new Handler(thread.getLooper());
+    }
+
+    /**
+     * Resets frame times in order to calculate fps for different test pass.
+     */
+    public void resetFrameTimes() {
+        synchronized (mSurfaceLock) {
+            mFrameTimes.clear();
+        }
+    }
+
+    /**
+     * Returns current fps based on collected frame times.
+     */
+    public double getFps() {
+        synchronized (mSurfaceLock) {
+            if (mFrameTimes.size() < 2) {
+                return 0.0f;
+            }
+            return 1000.0 * mFrameTimes.size() /
+                    (mFrameTimes.get(mFrameTimes.size() - 1) - mFrameTimes.get(0));
+        }
+    }
+
+    /**
+     * Invalidates surface.
+     * @param traceCalls set to true in case we need register trace calls. Not used for warm-up.
+     * @param drawFps perform drawing current fps on surface to have some payload on surface.
+     */
+    public void invalidateSurface(boolean traceCalls, boolean drawFps) {
+        synchronized (mSurfaceLock) {
+            if (mSurface == null) {
+                throw new IllegalStateException("Surface is not ready");
+            }
+            if (traceCalls) {
+                Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, LOCAL_REQUEST_BUFFER);
+            }
+            Canvas canvas = mSurface.lockCanvas(null);
+            if (traceCalls) {
+                Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+            }
+
+            if (drawFps) {
+                int textSize = canvas.getHeight() / 24;
+                Paint paint = new Paint();
+                paint.setTextSize(textSize);
+                paint.setColor(0xFFFF8040);
+                canvas.drawARGB(92, 255, 255, 255);
+                canvas.drawText("FPS: " + String.format("%.2f", getFps()), 10, 300, paint);
+            }
+
+            if (traceCalls) {
+                Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, LOCAL_POST_BUFFER);
+            }
+            mSurface.unlockCanvasAndPost(canvas);
+            if (traceCalls) {
+                Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
+            }
+
+            mFrameTimes.add(System.currentTimeMillis());
+        }
+    }
+
+    /**
+     * Wait until surface is created and ready to use or return immediately if surface
+     * already exists.
+     */
+    public void waitForSurfaceReady() {
+        synchronized (mSurfaceLock) {
+            if (mSurface == null) {
+                try {
+                    mSurfaceLock.wait(5000);
+                } catch(InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (mSurface == null)
+                throw new IllegalStateException("Surface is not ready.");
+        }
+    }
+
+    /**
+     * Waits until surface is destroyed or return immediately if surface does not exist.
+     */
+    public void waitForSurfaceDestroyed() {
+        synchronized (mSurfaceLock) {
+            if (mSurface != null) {
+                try {
+                    mSurfaceLock.wait(5000);
+                } catch(InterruptedException e) {
+                }
+            }
+            if (mSurface != null)
+                throw new IllegalStateException("Surface still exists.");
+        }
+    }
+
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+        // This method is always called at least once, after surfaceCreated.
+        synchronized (mSurfaceLock) {
+            mSurface = holder.getSurface();
+            mSurfaceLock.notify();
+            mHandler.post(mInvalidateSurfaceTask);
+        }
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        synchronized (mSurfaceLock) {
+            mHandler.removeCallbacks(mInvalidateSurfaceTask);
+            mSurface = null;
+            mSurfaceLock.notify();
+        }
+    }
+}
diff --git a/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java b/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java
new file mode 100644
index 0000000..b0e6196
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/GamePerformanceActivity.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2018 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.gameperformance;
+
+import java.util.concurrent.CountDownLatch;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.RelativeLayout;
+
+/**
+ * Minimal activity that holds SurfaceView or GLSurfaceView.
+ * call attachSurfaceView or attachOpenGLView to switch views.
+ */
+public class GamePerformanceActivity extends Activity {
+    private CustomSurfaceView mSurfaceView = null;
+    private CustomOpenGLView mOpenGLView = null;
+    private RelativeLayout mRootLayout;
+
+    public void attachSurfaceView() throws InterruptedException {
+        synchronized (mRootLayout) {
+            if (mSurfaceView != null) {
+                return;
+            }
+            final CountDownLatch latch = new CountDownLatch(1);
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (mOpenGLView != null) {
+                        mRootLayout.removeView(mOpenGLView);
+                        mOpenGLView = null;
+                    }
+                    mSurfaceView = new CustomSurfaceView(GamePerformanceActivity.this);
+                    mRootLayout.addView(mSurfaceView);
+                    latch.countDown();
+                }
+            });
+            latch.await();
+            mSurfaceView.waitForSurfaceReady();
+        }
+    }
+
+    public void attachOpenGLView() throws InterruptedException {
+        synchronized (mRootLayout) {
+            if (mOpenGLView != null) {
+                return;
+            }
+            final CountDownLatch latch = new CountDownLatch(1);
+            runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (mSurfaceView != null) {
+                        mRootLayout.removeView(mSurfaceView);
+                        mSurfaceView = null;
+                    }
+                    mOpenGLView = new CustomOpenGLView(GamePerformanceActivity.this);
+                    mRootLayout.addView(mOpenGLView);
+                    latch.countDown();
+                }
+            });
+            latch.await();
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+        // To layouts in parent. First contains list of Surfaces and second
+        // controls. Controls stay on top.
+        mRootLayout = new RelativeLayout(this);
+        mRootLayout.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT));
+
+        Rect rect = new Rect();
+        getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
+
+        mOpenGLView =  new CustomOpenGLView(this);
+        mRootLayout.addView(mOpenGLView);
+
+        setContentView(mRootLayout);
+    }
+
+    public void resetFrameTimes() {
+        if (mSurfaceView != null) {
+            mSurfaceView.resetFrameTimes();
+        } else if (mOpenGLView != null) {
+            mOpenGLView.resetFrameTimes();
+        } else {
+            throw new IllegalStateException("Nothing attached");
+        }
+    }
+
+    public double getFps() {
+        if (mSurfaceView != null) {
+            return mSurfaceView.getFps();
+        } else if (mOpenGLView != null) {
+            return mOpenGLView.getFps();
+        } else {
+            throw new IllegalStateException("Nothing attached");
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+}
\ No newline at end of file
diff --git a/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java
new file mode 100644
index 0000000..e5de7d7
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/GamePerformanceTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 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.gameperformance;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Trace;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+public class GamePerformanceTest extends
+        ActivityInstrumentationTestCase2<GamePerformanceActivity> {
+    private final static String TAG = "GamePerformanceTest";
+
+    private final static int GRAPHIC_BUFFER_WARMUP_LOOP_CNT = 60;
+
+    public GamePerformanceTest() {
+        super(GamePerformanceActivity.class);
+    }
+
+    @SmallTest
+    public void testGraphicBufferMetrics() throws IOException, InterruptedException {
+        Bundle status = new Bundle();
+
+        for (int i = 0; i < 2; ++i) {
+            if (i == 0) {
+                getActivity().attachSurfaceView();
+            } else {
+                getActivity().attachOpenGLView();
+            }
+
+            // Perform warm-up.
+            Thread.sleep(2000);
+
+            // Once atrace is done, this one is triggered.
+            CountDownLatch latch = new CountDownLatch(1);
+
+            final String passTag = i == 0 ? "surface" : "opengl";
+            final String output = (new File(getInstrumentation().getContext().getFilesDir(),
+                    "atrace_" + passTag + ".log")).getAbsolutePath();
+            Log.i(TAG, "Collecting traces to " + output);
+            new ATraceRunner(getInstrumentation(), output, 5, "gfx", new ATraceRunner.Delegate() {
+                @Override
+                public void onProcessed(boolean success) {
+                    latch.countDown();
+                }
+            }).execute();
+
+            // Reset frame times and perform invalidation loop while atrace is running.
+            getActivity().resetFrameTimes();
+            latch.await();
+
+            // Copy results.
+            final Map<String, Double> metrics =
+                    GraphicBufferMetrics.processGraphicBufferResult(output, passTag);
+            for (Map.Entry<String, Double> metric : metrics.entrySet()) {
+                status.putDouble(metric.getKey(), metric.getValue());
+            }
+            // Also record FPS.
+            status.putDouble(passTag + "_fps", getActivity().getFps());
+        }
+
+        getInstrumentation().sendStatus(Activity.RESULT_OK, status);
+    }
+}
diff --git a/tests/GamePerformance/src/android/gameperformance/GraphicBufferMetrics.java b/tests/GamePerformance/src/android/gameperformance/GraphicBufferMetrics.java
new file mode 100644
index 0000000..dffce1a
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/GraphicBufferMetrics.java
@@ -0,0 +1,530 @@
+/*
+ * Copyright (C) 2018 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.gameperformance;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+/**
+ * Utility class that performs analysis of atrace logs. This is implemented without Android
+ * dependencies and therefore can be used in stand-alone mode.
+ * Idea of this is to track atrace gfx event from graphics buffer producer/consumer.
+ * We analyze here from surfaceflinger
+ *   queueBuffer - event when buffer was queued.
+ *   acquireBuffer - event when buffer was requested for composition.
+ *   releaseBuffer - even when buffer was released after composition.
+ * This also track events, issued locally
+ *   localPostBuffer - event when buffer was posted from the local app.
+ *
+ *  queueBuffer, acquireBuffer, releaseBuffer is accompanied with buffer name so we
+ *  can track life-cycle of particular buffer.
+ *  We don't have such information for localPostBuffer, however we can track next queueBuffer
+ *  from surfaceflinger corresponds to previous localPostBuffer.
+ *
+ *  Following results are calculated:
+ *    post_time_[min/max/avr]_mcs - time for localPostBuffer duration.
+ *    ready_time_[min/max/avr]_mcs - time from localPostBuffer to when buffer was acquired by
+ *                                   surfaceflinger.
+ *    latency_[min/max/avr]_mcs - time from localPostBuffer to when buffer was released by
+ *                                 surfaceflinger.
+ *    missed_frame_percents - percentage of missed frames (frames that do not have right sequence
+ *                            of events).
+ *
+ * Following is example of atrace logs from different platforms
+ *            <...>-5237  (-----) [000] ...1   228.380392: tracing_mark_write: B|11|SurfaceView - android.gameperformance/android.gameperformance.GamePerformanceActivity#0: 2
+ *   surfaceflinger-5855  ( 5855) [001] ...1   169.627364: tracing_mark_write: B|24|acquireBuffer
+ *   HwBinder:617_2-652   (  617) [002] d..1 360262.921756: sde_evtlog: 617|sde_encoder_virt_atomic_check:855|19|0|0|0|0|0|0|0|0|0|0|0|0|0|0
+ */
+public class GraphicBufferMetrics {
+    private final static String TAG = "GraphicBufferMetrics";
+
+    private final static String KEY_POST_TIME = "post_time";
+    private final static String KEY_READY_TIME = "ready_time";
+    private final static String KEY_LATENCY = "latency";
+    private final static String SUFFIX_MIN = "min";
+    private final static String SUFFIX_MAX = "max";
+    private final static String SUFFIX_MEDIAN = "median";
+    private final static String KEY_MISSED_FRAME_RATE = "missed_frame_percents";
+
+    private final static int EVENT_POST_BUFFER = 0;
+    private final static int EVENT_QUEUE_BUFFER = 1;
+    private final static int EVENT_ACQUIRE_BUFFER = 2;
+    private final static int EVENT_RELEASE_BUFFER = 3;
+
+    // atrace prints this line. Used as a marker to make sure that we can parse its output.
+    private final static String ATRACE_HEADER =
+            "#           TASK-PID    TGID   CPU#  ||||    TIMESTAMP  FUNCTION";
+
+    private final static String[] KNOWN_PHRASES = new String[] {
+            "capturing trace... done", "TRACE:"};
+    private final static List<String> KNWON_PHRASES_LIST = Arrays.asList(KNOWN_PHRASES);
+
+    // Type of the atrace event we can parse and analyze.
+    private final static String FUNCTION_TRACING_MARK_WRITE = "tracing_mark_write";
+
+    // Trace event we can ignore. It contains current timestamp information for the atrace output.
+    private final static String TRACE_EVENT_CLOCK_SYNC = "trace_event_clock_sync:";
+
+    // Threshold we consider test passes successfully. If we cannot collect enough amount of frames
+    // let fail the test. 50 is calculated 10 frames per second running for five seconds.
+    private final static int MINIMAL_SAMPLE_CNT_TO_PASS = 50;
+
+    /**
+     * Raw event in atrace. Stored hierarchically.
+     */
+    private static class RawEvent {
+        // Parent of this event or null for the root holder.
+        public final RawEvent mParent;
+        // Time of the event in mcs.
+        public final long mTime;
+        // Duration of the event in mcs.
+        public long mDuration;
+        // Name/body of the event.
+        public final String mName;
+        // Children events.
+        public final List<RawEvent> mChildren;
+
+        public RawEvent(RawEvent parent, long time, String name) {
+            mParent = parent;
+            mTime = time;
+            mName = name;
+            mDuration = -1;
+            mChildren = new ArrayList<>();
+        }
+
+        /**
+         * Recursively finds child events.
+         * @param path specify path to events. For example a/b. That means to find child with name
+         *             'a' of the current event and in this child find child with name 'b'. Path
+         *             can consist from only one segment and that means we analyze only children of
+         *             the current event.
+         * @param collector to collect found events.
+         */
+        public void findEvents(String path, List<RawEvent> collector) {
+            final int separator = path.indexOf('/');
+            final String current = separator > 0 ? path.substring(0, separator) : path;
+            final String nextPath = separator > 0 ? path.substring(separator + 1) : null;
+            for (RawEvent child : mChildren) {
+                if (child.mName.equals(current)) {
+                    if (nextPath != null) {
+                        child.findEvents(nextPath, collector);
+                    } else {
+                        collector.add(child);
+                    }
+                }
+            }
+        }
+
+        public void dump(String prefix) {
+            System.err.print(prefix);
+            System.err.println(mTime + "[" + mDuration + "] " + mName);
+            for (RawEvent e : mChildren) {
+                e.dump(prefix + "  ");
+            }
+        }
+    }
+
+    /**
+     * Describes graphic buffer event. local post, queued, acquired, released.
+     */
+    private static class BufferEvent {
+        public final int mType;
+        public final long mTime;
+        public final long mDuration;
+        public final String mBufferId;
+
+        public BufferEvent(int type, long time, long duration, String bufferId) {
+            mType = type;
+            mTime = time;
+            mDuration = duration;
+            mBufferId = bufferId;
+        }
+
+        @Override
+        public String toString() {
+            return "Type: " + mType + ". Time: " + mTime +
+                    "[" + mDuration + "]. Buffer: " + mBufferId + ".";
+        }
+    }
+
+    /**
+     * Returns true if given char is digit.
+     */
+    private static boolean isDigitChar(char c) {
+        return (c >= '0') && (c <= '9');
+    }
+
+    /**
+     * Returns true if given char is digit or '.'.
+     */
+    private static boolean isDoubleDigitChar(char c) {
+        return (c == '.') || isDigitChar(c);
+    }
+
+    /**
+     * Convert timestamp string that represents double value in seconds to long time that represents
+     * timestamp in microseconds.
+     */
+    private static long getTimeStamp(String timeStampStr) {
+        return (long)(1000000.0 * Double.parseDouble(timeStampStr));
+    }
+
+    /**
+     * Reads atrace log and build event model. Result is a map, where key specifies event for the
+     * particular thread. Value is the synthetic root RawEvent that holds all events for the
+     * thread. Events are stored hierarchically.
+     */
+    private static Map<Integer, RawEvent> buildEventModel(String fileName) throws IOException {
+        Map<Integer, RawEvent> result = new HashMap<>();
+
+        BufferedReader bufferedReader = null;
+        String line = "";
+        boolean headerDetected = false;
+        try {
+            bufferedReader = new BufferedReader(new FileReader(fileName));
+            while ((line = bufferedReader.readLine()) != null) {
+                // Make sure we find comment that describes output format we can with.
+                headerDetected |= line.equals(ATRACE_HEADER);
+                // Skip comments.
+                if (line.startsWith("#")) {
+                    continue;
+                }
+                // Skip known service output
+                if (KNWON_PHRASES_LIST.contains(line)) {
+                    continue;
+                }
+
+                if (!headerDetected) {
+                    // We don't know the format of this line.
+                    throw new IllegalStateException("Header was not detected");
+                }
+
+                // TASK-PID in header exists at position 12. PID position 17 should contains first
+                // digit of thread id after the '-'.
+                if (!isDigitChar(line.charAt(17)) || line.charAt(16) != '-') {
+                    throw new IllegalStateException("Failed to parse thread id: " + line);
+                }
+                int rightIndex = line.indexOf(' ', 17);
+                final String threadIdStr = line.substring(17, rightIndex);
+                final int threadId = Integer.parseInt(threadIdStr);
+
+                // TIMESTAMP in header exists at position 45
+                // This position should point in the middle of timestamp which is ended by ':'.
+                int leftIndex = 45;
+                while (isDoubleDigitChar(line.charAt(leftIndex))) {
+                    --leftIndex;
+                }
+                rightIndex = line.indexOf(':', 45);
+
+                final String timeStampString = line.substring(leftIndex + 1, rightIndex);
+                final long timeStampMcs = getTimeStamp(timeStampString);
+
+                // Find function name, pointed by FUNCTION. Long timestamp can shift if position
+                // so use end of timestamp to find the function which is ended by ':'.
+                leftIndex = rightIndex + 1;
+                while (Character.isWhitespace(line.charAt(leftIndex))) {
+                    ++leftIndex;
+                }
+                rightIndex = line.indexOf(':', leftIndex);
+                final String function = line.substring(leftIndex, rightIndex);
+
+                if (!function.equals(FUNCTION_TRACING_MARK_WRITE)) {
+                    continue;
+                }
+
+                // Rest of the line is event body.
+                leftIndex = rightIndex + 1;
+                while (Character.isWhitespace(line.charAt(leftIndex))) {
+                    ++leftIndex;
+                }
+
+                final String event = line.substring(leftIndex);
+                if (event.startsWith(TRACE_EVENT_CLOCK_SYNC)) {
+                    continue;
+                }
+
+                // Parse event, for example:
+                // B|615|SurfaceView - android.gameperformance.GamePerformanceActivity#0: 1
+                // E|615
+                // C|11253|operation id|2
+                StringTokenizer eventTokenizer = new StringTokenizer(event, "|");
+                final String eventType = eventTokenizer.nextToken();
+
+                // Attach root on demand.
+                if (!result.containsKey(threadId)) {
+                    result.put(threadId, new RawEvent(null /* parent */,
+                                                      timeStampMcs,
+                                                      "#ROOT_" + threadId));
+                }
+
+                switch (eventType) {
+                case "B": {
+                        // Log entry starts.
+                        eventTokenizer.nextToken(); // PID
+                        String eventText = eventTokenizer.nextToken();
+                        while (eventTokenizer.hasMoreTokens()) {
+                            eventText += " ";
+                            eventText += eventTokenizer.nextToken();
+                        }
+                        RawEvent parent = result.get(threadId);
+                        RawEvent current = new RawEvent(parent, timeStampMcs, eventText);
+                        parent.mChildren.add(current);
+                        result.put(threadId, current);
+                    }
+                    break;
+                case "E": {
+                        // Log entry ends.
+                        RawEvent current = result.get(threadId);
+                        current.mDuration = timeStampMcs - current.mTime;
+                        if (current.mParent == null) {
+                            // Detect a tail of the previous call. Remove last child element if it
+                            // exists once it does not belong to the root.
+                            if (!current.mChildren.isEmpty()) {
+                                current.mChildren.remove(current.mChildren.size() -1);
+                            }
+                        } else {
+                            result.put(threadId, current.mParent);
+                        }
+                    }
+                    break;
+                case "C":
+                    // Counter, ignore
+                    break;
+                default:
+                    throw new IllegalStateException(
+                            "Unrecognized trace: " + line + " # " + eventType + " # " + event);
+                }
+            }
+
+            // Detect incomplete events and detach from the root.
+            Set<Integer> threadIds = new TreeSet<>();
+            threadIds.addAll(result.keySet());
+            for (int threadId : threadIds) {
+                RawEvent root = result.get(threadId);
+                if (root.mParent == null) {
+                    // Last trace was closed.
+                    continue;
+                }
+                // Find the root.
+                while (root.mParent != null) {
+                    root = root.mParent;
+                }
+                // Discard latest incomplete element.
+                root.mChildren.remove(root.mChildren.size() - 1);
+                result.put(threadId, root);
+            }
+        } catch (Exception e) {
+            throw new IOException("Failed to process " + line, e);
+        } finally {
+            Utils.closeQuietly(bufferedReader);
+        }
+
+        return result;
+    }
+
+    /**
+     * Processes provided atrace log and calculates graphics buffer metrics.
+     * @param fileName name of atrace log file.
+     * @param testTag tag to separate results for the different passes.
+     */
+    public static Map<String, Double> processGraphicBufferResult(
+            String fileName, String testTag) throws IOException {
+        final Map<Integer, RawEvent> model = buildEventModel(fileName);
+
+        List<RawEvent> collectorPostBuffer = new ArrayList<>();
+        List<RawEvent> collectorQueueBuffer = new ArrayList<>();
+        List<RawEvent> collectorReleaseBuffer = new ArrayList<>();
+        List<RawEvent> collectorAcquireBuffer = new ArrayList<>();
+
+        // Collect required events.
+        for (RawEvent root : model.values()) {
+            // Surface view
+            root.findEvents("localPostBuffer", collectorPostBuffer);
+            // OpengGL view
+            root.findEvents("eglSwapBuffersWithDamageKHR", collectorPostBuffer);
+
+            root.findEvents("queueBuffer", collectorQueueBuffer);
+            root.findEvents("onMessageReceived/handleMessageInvalidate/latchBuffer/" +
+                    "updateTexImage/acquireBuffer",
+                    collectorAcquireBuffer);
+            // PI stack
+            root.findEvents(
+                    "onMessageReceived/handleMessageRefresh/postComposition/releaseBuffer",
+                    collectorReleaseBuffer);
+            // NYC stack
+            root.findEvents(
+                    "onMessageReceived/handleMessageRefresh/releaseBuffer",
+                    collectorReleaseBuffer);
+        }
+
+        // Convert raw event to buffer events.
+        List<BufferEvent> bufferEvents = new ArrayList<>();
+        for (RawEvent event : collectorPostBuffer) {
+            bufferEvents.add(
+                    new BufferEvent(EVENT_POST_BUFFER, event.mTime, event.mDuration, null));
+        }
+        toBufferEvents(EVENT_QUEUE_BUFFER, collectorQueueBuffer, bufferEvents);
+        toBufferEvents(EVENT_ACQUIRE_BUFFER, collectorAcquireBuffer, bufferEvents);
+        toBufferEvents(EVENT_RELEASE_BUFFER, collectorReleaseBuffer, bufferEvents);
+
+        // Sort events based on time. These events are originally taken from different threads so
+        // order is not always preserved.
+        Collections.sort(bufferEvents, new Comparator<BufferEvent>() {
+            @Override
+            public int compare(BufferEvent o1, BufferEvent o2) {
+                if (o1.mTime < o2.mTime) {
+                    return -1;
+                } if (o1.mTime > o2.mTime) {
+                    return +1;
+                } else {
+                    return 0;
+                }
+            }
+        });
+
+        // Collect samples.
+        List<Long> postTimes = new ArrayList<>();
+        List<Long> readyTimes = new ArrayList<>();
+        List<Long> latencyTimes = new ArrayList<>();
+        long missedCnt = 0;
+
+        for (int i = 0; i < bufferEvents.size(); ++i) {
+            if (bufferEvents.get(i).mType != EVENT_POST_BUFFER) {
+                continue;
+            }
+            final int queueIndex = findNextOfType(bufferEvents, i + 1, EVENT_QUEUE_BUFFER);
+            if (queueIndex < 0) {
+                break;
+            }
+            final int acquireIndex = findNextOfBufferId(bufferEvents, queueIndex + 1,
+                    bufferEvents.get(queueIndex).mBufferId);
+            if (acquireIndex < 0) {
+                break;
+            }
+            if (bufferEvents.get(acquireIndex).mType != EVENT_ACQUIRE_BUFFER) {
+                // Was not actually presented.
+                ++missedCnt;
+                continue;
+            }
+            final int releaseIndex = findNextOfBufferId(bufferEvents, acquireIndex + 1,
+                    bufferEvents.get(queueIndex).mBufferId);
+            if (releaseIndex < 0) {
+                break;
+            }
+            if (bufferEvents.get(releaseIndex).mType != EVENT_RELEASE_BUFFER) {
+                // Was not actually presented.
+                ++missedCnt;
+                continue;
+            }
+
+            postTimes.add(bufferEvents.get(i).mDuration);
+            readyTimes.add(
+                    bufferEvents.get(acquireIndex).mTime - bufferEvents.get(i).mTime);
+            latencyTimes.add(
+                    bufferEvents.get(releaseIndex).mTime - bufferEvents.get(i).mTime);
+        }
+
+        if (postTimes.size() < MINIMAL_SAMPLE_CNT_TO_PASS) {
+            throw new IllegalStateException("Too few sample cnt: " + postTimes.size() +". " +
+                    MINIMAL_SAMPLE_CNT_TO_PASS + " is required.");
+        }
+
+        Map<String, Double> status = new TreeMap<>();
+        addResults(status, testTag, KEY_POST_TIME, postTimes);
+        addResults(status, testTag, KEY_READY_TIME, readyTimes);
+        addResults(status, testTag, KEY_LATENCY, latencyTimes);
+        status.put(testTag + "_" + KEY_MISSED_FRAME_RATE,
+                100.0 * missedCnt / (missedCnt + postTimes.size()));
+        return status;
+    }
+
+    private static void addResults(
+            Map<String, Double> status, String tag, String key, List<Long> times) {
+        Collections.sort(times);
+        long min = times.get(0);
+        long max = times.get(0);
+        for (long time : times) {
+            min = Math.min(min, time);
+            max = Math.max(max, time);
+        }
+        status.put(tag + "_" + key + "_" + SUFFIX_MIN, (double)min);
+        status.put(tag + "_" + key + "_" + SUFFIX_MAX, (double)max);
+        status.put(tag + "_" + key + "_" + SUFFIX_MEDIAN, (double)times.get(times.size() / 2));
+    }
+
+    // Helper to convert surface flinger events to buffer events.
+    private static void toBufferEvents(
+            int type, List<RawEvent> rawEvents, List<BufferEvent> bufferEvents) {
+        for (RawEvent event : rawEvents) {
+            if (event.mChildren.isEmpty()) {
+                throw new IllegalStateException("Buffer name is expected");
+            }
+            final String bufferName = event.mChildren.get(0).mName;
+            if (bufferName.startsWith("SurfaceView - android.gameperformance")) {
+                bufferEvents.add(
+                        new BufferEvent(type, event.mTime, event.mDuration, bufferName));
+            }
+        }
+    }
+
+    private static int findNextOfType(List<BufferEvent> events, int startIndex, int type) {
+        for (int i = startIndex; i < events.size(); ++i) {
+            if (events.get(i).mType == type) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private static int findNextOfBufferId(
+            List<BufferEvent> events, int startIndex, String bufferId) {
+        for (int i = startIndex; i < events.size(); ++i) {
+            if (bufferId.equals(events.get(i).mBufferId)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    public static void main(String[] args) {
+        if (args.length != 1) {
+            System.err.println("Usage: " + TAG + " atrace.log");
+            return;
+        }
+
+        try {
+            System.out.println("Results:");
+            for (Map.Entry<?, ?> entry :
+                processGraphicBufferResult(args[0], "default").entrySet()) {
+                System.out.println("    " + entry.getKey() + " = " + entry.getValue());
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/tests/GamePerformance/src/android/gameperformance/Utils.java b/tests/GamePerformance/src/android/gameperformance/Utils.java
new file mode 100644
index 0000000..6481971
--- /dev/null
+++ b/tests/GamePerformance/src/android/gameperformance/Utils.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2018 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.gameperformance;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+public class Utils {
+    public static void closeQuietly(Closeable closeable) {
+        try {
+            if (closeable != null) {
+                closeable.close();
+            }
+        } catch (IOException e) {
+            // Ignore
+        }
+    }
+}