Merge "Add handle suppression to learned state"
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6b931b8..5ab694f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -189,7 +189,6 @@
 import java.lang.ref.WeakReference;
 import java.lang.reflect.Method;
 import java.net.InetAddress;
-import java.nio.file.Files;
 import java.text.DateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -7293,24 +7292,6 @@
                 super.remove(path);
             }
         }
-
-        @Override
-        public void rename(String oldPath, String newPath) throws ErrnoException {
-            try {
-                super.rename(oldPath, newPath);
-            } catch (ErrnoException e) {
-                if (e.errno == OsConstants.EXDEV) {
-                    Log.v(TAG, "Recovering failed rename " + oldPath + " to " + newPath);
-                    try {
-                        Files.move(new File(oldPath).toPath(), new File(newPath).toPath());
-                    } catch (IOException e2) {
-                        throw e;
-                    }
-                } else {
-                    throw e;
-                }
-            }
-        }
     }
 
     public static void main(String[] args) {
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index e8b3ca6..1456ff7 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.media.AudioAttributes;
 import android.os.VibrationEffect;
 
 /** {@hide} */
@@ -23,8 +24,8 @@
 {
     boolean hasVibrator();
     boolean hasAmplitudeControl();
-    void vibrate(int uid, String opPkg, in VibrationEffect effect, int usageHint, String reason,
-            IBinder token);
+    void vibrate(int uid, String opPkg, in VibrationEffect effect, in AudioAttributes attributes,
+            String reason, IBinder token);
     void cancelVibrate(IBinder token);
 }
 
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index 4af514a..a5188e7 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -77,16 +77,12 @@
             return;
         }
         try {
-            mService.vibrate(uid, opPkg, effect, usageForAttributes(attributes), reason, mToken);
+            mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
         } catch (RemoteException e) {
             Log.w(TAG, "Failed to vibrate.", e);
         }
     }
 
-    private static int usageForAttributes(AudioAttributes attributes) {
-        return attributes != null ? attributes.getUsage() : AudioAttributes.USAGE_UNKNOWN;
-    }
-
     @Override
     public void cancel() {
         if (mService == null) {
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index bf0ef94..2c2c295 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1122,6 +1122,9 @@
             if (limit > lineEnd) {
                 limit = lineEnd;
             }
+            if (limit == start) {
+                continue;
+            }
             level[limit - lineStart - 1] =
                     (byte) ((runs[i + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK);
         }
@@ -1220,8 +1223,8 @@
     }
 
     /**
-     * Computes in linear time the results of calling
-     * #getHorizontal for all offsets on a line.
+     * Computes in linear time the results of calling #getHorizontal for all offsets on a line.
+     *
      * @param line The line giving the offsets we compute information for
      * @param clamped Whether to clamp the results to the width of the layout
      * @param primary Whether the results should be the primary or the secondary horizontal
@@ -1257,7 +1260,7 @@
         TextLine.recycle(tl);
 
         if (clamped) {
-            for (int offset = 0; offset <= wid.length; ++offset) {
+            for (int offset = 0; offset < wid.length; ++offset) {
                 if (wid[offset] > mWidth) {
                     wid[offset] = mWidth;
                 }
diff --git a/core/java/android/util/TimingsTraceLog.java b/core/java/android/util/TimingsTraceLog.java
index bc21722..5370645 100644
--- a/core/java/android/util/TimingsTraceLog.java
+++ b/core/java/android/util/TimingsTraceLog.java
@@ -21,10 +21,10 @@
 import android.os.SystemClock;
 import android.os.Trace;
 
-import java.util.ArrayDeque;
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Deque;
 import java.util.List;
 
 /**
@@ -37,16 +37,37 @@
 public class TimingsTraceLog {
     // Debug boot time for every step if it's non-user build.
     private static final boolean DEBUG_BOOT_TIME = !Build.IS_USER;
-    private final Deque<Pair<String, Long>> mStartTimes =
-            DEBUG_BOOT_TIME ? new ArrayDeque<>() : null;
+
+    // Maximum number of nested calls that are stored
+    private static final int MAX_NESTED_CALLS = 10;
+
+    private final String[] mStartNames;
+    private final long[] mStartTimes;
+
     private final String mTag;
-    private long mTraceTag;
-    private long mThreadId;
+    private final long mTraceTag;
+    private final long mThreadId;
+    private final int mMaxNestedCalls;
+
+    private int mCurrentLevel = -1;
 
     public TimingsTraceLog(String tag, long traceTag) {
+        this(tag, traceTag, DEBUG_BOOT_TIME ? MAX_NESTED_CALLS : -1);
+    }
+
+    @VisibleForTesting
+    public TimingsTraceLog(String tag, long traceTag, int maxNestedCalls) {
         mTag = tag;
         mTraceTag = traceTag;
         mThreadId = Thread.currentThread().getId();
+        mMaxNestedCalls = maxNestedCalls;
+        if (maxNestedCalls > 0) {
+            mStartNames = new String[maxNestedCalls];
+            mStartTimes = new long[maxNestedCalls];
+        } else {
+            mStartNames = null;
+            mStartTimes = null;
+        }
     }
 
     /**
@@ -56,27 +77,41 @@
     public void traceBegin(String name) {
         assertSameThread();
         Trace.traceBegin(mTraceTag, name);
-        if (DEBUG_BOOT_TIME) {
-            mStartTimes.push(Pair.create(name, SystemClock.elapsedRealtime()));
+
+        if (!DEBUG_BOOT_TIME) return;
+
+        if (mCurrentLevel + 1 >= mMaxNestedCalls) {
+            Slog.w(mTag, "not tracing duration of '" + name + "' because already reached "
+                    + mMaxNestedCalls + " levels");
+            return;
         }
+
+        mCurrentLevel++;
+        mStartNames[mCurrentLevel] = name;
+        mStartTimes[mCurrentLevel] = SystemClock.elapsedRealtime();
     }
 
     /**
      * End tracing previously {@link #traceBegin(String) started} section.
-     * Also {@link #logDuration logs} the duration.
+     *
+     * <p>Also {@link #logDuration logs} the duration.
      */
     public void traceEnd() {
         assertSameThread();
         Trace.traceEnd(mTraceTag);
-        if (!DEBUG_BOOT_TIME) {
-            return;
-        }
-        if (mStartTimes.peek() == null) {
+
+        if (!DEBUG_BOOT_TIME) return;
+
+        if (mCurrentLevel < 0) {
             Slog.w(mTag, "traceEnd called more times than traceBegin");
             return;
         }
-        Pair<String, Long> event = mStartTimes.pop();
-        logDuration(event.first, (SystemClock.elapsedRealtime() - event.second));
+
+        final String name = mStartNames[mCurrentLevel];
+        final long duration = SystemClock.elapsedRealtime() - mStartTimes[mCurrentLevel];
+        mCurrentLevel--;
+
+        logDuration(name, duration);
     }
 
     private void assertSameThread() {
@@ -89,7 +124,7 @@
     }
 
     /**
-     * Log the duration so it can be parsed by external tools for performance reporting
+     * Logs a duration so it can be parsed by external tools for performance reporting.
      */
     public void logDuration(String name, long timeMs) {
         Slog.d(mTag, name + " took to complete: " + timeMs + "ms");
@@ -105,10 +140,11 @@
      */
     @NonNull
     public final List<String> getUnfinishedTracesForDebug() {
-        if (mStartTimes == null || mStartTimes.isEmpty()) return Collections.emptyList();
-
-        final ArrayList<String> stack = new ArrayList<>(mStartTimes.size());
-        mStartTimes.descendingIterator().forEachRemaining((pair) -> stack.add(pair.first));
-        return stack;
+        if (mStartTimes == null || mCurrentLevel < 0) return Collections.emptyList();
+        final ArrayList<String> list = new ArrayList<>(mCurrentLevel + 1);
+        for (int i = 0; i <= mCurrentLevel; i++) {
+            list.add(mStartNames[i]);
+        }
+        return list;
     }
 }
diff --git a/core/jni/android/graphics/SurfaceTexture.cpp b/core/jni/android/graphics/SurfaceTexture.cpp
index 3e464c6..73dc656 100644
--- a/core/jni/android/graphics/SurfaceTexture.cpp
+++ b/core/jni/android/graphics/SurfaceTexture.cpp
@@ -131,13 +131,6 @@
     return (IGraphicBufferProducer*)env->GetLongField(thiz, fields.producer);
 }
 
-sp<ANativeWindow> android_SurfaceTexture_getNativeWindow(JNIEnv* env, jobject thiz) {
-    sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
-    sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, thiz));
-    sp<Surface> surfaceTextureClient(surfaceTexture != NULL ? new Surface(producer) : NULL);
-    return surfaceTextureClient;
-}
-
 bool android_SurfaceTexture_isInstanceOf(JNIEnv* env, jobject thiz) {
     jclass surfaceTextureClass = env->FindClass(kSurfaceTextureClassPathName);
     return env->IsInstanceOf(thiz, surfaceTextureClass);
diff --git a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
index 719cf74..87adbe8 100644
--- a/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
+++ b/core/jni/android_hardware_camera2_legacy_LegacyCameraDevice.cpp
@@ -26,6 +26,7 @@
 #include "core_jni_helpers.h"
 #include "android_runtime/android_view_Surface.h"
 #include "android_runtime/android_graphics_SurfaceTexture.h"
+#include "surfacetexture/SurfaceTexture.h"
 
 #include <gui/Surface.h>
 #include <gui/IGraphicBufferProducer.h>
@@ -394,10 +395,17 @@
     return anw;
 }
 
+static sp<ANativeWindow> getSurfaceTextureNativeWindow(JNIEnv* env, jobject thiz) {
+    sp<SurfaceTexture> surfaceTexture(SurfaceTexture_getSurfaceTexture(env, thiz));
+    sp<IGraphicBufferProducer> producer(SurfaceTexture_getProducer(env, thiz));
+    sp<Surface> surfaceTextureClient(surfaceTexture != NULL ? new Surface(producer) : NULL);
+    return surfaceTextureClient;
+}
+
 static sp<ANativeWindow> getNativeWindowFromTexture(JNIEnv* env, jobject surfaceTexture) {
     sp<ANativeWindow> anw;
     if (surfaceTexture) {
-        anw = android_SurfaceTexture_getNativeWindow(env, surfaceTexture);
+        anw = getSurfaceTextureNativeWindow(env, surfaceTexture);
         if (env->ExceptionCheck()) {
             return NULL;
         }
diff --git a/core/jni/include/android_runtime/android_graphics_SurfaceTexture.h b/core/jni/include/android_runtime/android_graphics_SurfaceTexture.h
index 0ad2587..d3ff959 100644
--- a/core/jni/include/android_runtime/android_graphics_SurfaceTexture.h
+++ b/core/jni/include/android_runtime/android_graphics_SurfaceTexture.h
@@ -17,8 +17,6 @@
 #ifndef _ANDROID_GRAPHICS_SURFACETEXTURE_H
 #define _ANDROID_GRAPHICS_SURFACETEXTURE_H
 
-#include <android/native_window.h>
-
 #include "jni.h"
 
 namespace android {
@@ -26,7 +24,6 @@
 class IGraphicBufferProducer;
 class SurfaceTexture;
 
-extern sp<ANativeWindow> android_SurfaceTexture_getNativeWindow(JNIEnv* env, jobject thiz);
 extern bool android_SurfaceTexture_isInstanceOf(JNIEnv* env, jobject thiz);
 
 /* Gets the underlying C++ SurfaceTexture object from a SurfaceTexture Java object. */
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index 990161a..93a6b15 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -743,6 +743,9 @@
         assertPrimaryIsTrailingPrevious(
                 RTL + LRI + RTL + LTR + PDI + RTL,
                 new boolean[]{false, false, true, false, false, false, false});
+        assertPrimaryIsTrailingPrevious(
+                "",
+                new boolean[]{false});
     }
 }
 
diff --git a/core/tests/coretests/src/android/util/TimingsTraceLogTest.java b/core/tests/coretests/src/android/util/TimingsTraceLogTest.java
deleted file mode 100644
index f76471c..0000000
--- a/core/tests/coretests/src/android/util/TimingsTraceLogTest.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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.
- */
-
-package android.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.Trace;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Tests for {@link TimingsTraceLog}.
- *
- * <p>Usage: {@code atest FrameworksCoreTests:android.util.TimingsTraceLogTest}
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class TimingsTraceLogTest {
-
-    @Test
-    public void testDifferentThreads() throws Exception {
-        TimingsTraceLog log = new TimingsTraceLog("TEST", Trace.TRACE_TAG_APP);
-        // Should be able to log on the same thread
-        log.traceBegin("test");
-        log.traceEnd();
-        final List<String> errors = new ArrayList<>();
-        // Calling from a different thread should fail
-        Thread t = new Thread(() -> {
-            try {
-                log.traceBegin("test");
-                errors.add("traceBegin should fail on a different thread");
-            } catch (IllegalStateException expected) {
-            }
-            try {
-                log.traceEnd();
-                errors.add("traceEnd should fail on a different thread");
-            } catch (IllegalStateException expected) {
-            }
-            // Verify that creating a new log will work
-            TimingsTraceLog log2 = new TimingsTraceLog("TEST", Trace.TRACE_TAG_APP);
-            log2.traceBegin("test");
-            log2.traceEnd();
-
-        });
-        t.start();
-        t.join();
-        assertThat(errors).isEmpty();
-    }
-
-    @Test
-    public void testGetUnfinishedTracesForDebug() {
-        TimingsTraceLog log = new TimingsTraceLog("TEST", Trace.TRACE_TAG_APP);
-        assertThat(log.getUnfinishedTracesForDebug()).isEmpty();
-
-        log.traceBegin("One");
-        assertThat(log.getUnfinishedTracesForDebug()).containsExactly("One").inOrder();
-
-        log.traceBegin("Two");
-        assertThat(log.getUnfinishedTracesForDebug()).containsExactly("One", "Two").inOrder();
-
-        log.traceEnd();
-        assertThat(log.getUnfinishedTracesForDebug()).containsExactly("One").inOrder();
-
-        log.traceEnd();
-        assertThat(log.getUnfinishedTracesForDebug()).isEmpty();
-    }
-}
diff --git a/core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java b/core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java
new file mode 100644
index 0000000..5dc44d2
--- /dev/null
+++ b/core/tests/mockingcoretests/src/android/util/TimingsTraceLogTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.
+ */
+package android.util;
+
+import static android.os.Trace.TRACE_TAG_APP;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.contains;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.matches;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import android.os.Trace;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.MockedVoidMethod;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoSession;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests for {@link TimingsTraceLog}.
+ *
+ * <p>Usage: {@code atest FrameworksMockingCoreTests:android.util.TimingsTraceLogTest}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TimingsTraceLogTest {
+
+    private static final String TAG = "TEST";
+
+    private MockitoSession mSession;
+
+    @Before
+    public final void startMockSession() {
+        mSession = mockitoSession()
+                .spyStatic(Slog.class)
+                .spyStatic(Trace.class)
+                .startMocking();
+    }
+
+    @After
+    public final void finishMockSession() {
+        mSession.finishMocking();
+    }
+
+    @Test
+    public void testDifferentThreads() throws Exception {
+        TimingsTraceLog log = new TimingsTraceLog(TAG, TRACE_TAG_APP);
+        // Should be able to log on the same thread
+        log.traceBegin("test");
+        log.traceEnd();
+        final List<String> errors = new ArrayList<>();
+        // Calling from a different thread should fail
+        Thread t = new Thread(() -> {
+            try {
+                log.traceBegin("test");
+                errors.add("traceBegin should fail on a different thread");
+            } catch (IllegalStateException expected) {
+            }
+            try {
+                log.traceEnd();
+                errors.add("traceEnd should fail on a different thread");
+            } catch (IllegalStateException expected) {
+            }
+            // Verify that creating a new log will work
+            TimingsTraceLog log2 = new TimingsTraceLog(TAG, TRACE_TAG_APP);
+            log2.traceBegin("test");
+            log2.traceEnd();
+
+        });
+        t.start();
+        t.join();
+        assertThat(errors).isEmpty();
+    }
+
+    @Test
+    public void testGetUnfinishedTracesForDebug() {
+        TimingsTraceLog log = new TimingsTraceLog("TEST", Trace.TRACE_TAG_APP);
+        assertThat(log.getUnfinishedTracesForDebug()).isEmpty();
+
+        log.traceBegin("One");
+        assertThat(log.getUnfinishedTracesForDebug()).containsExactly("One").inOrder();
+
+        log.traceBegin("Two");
+        assertThat(log.getUnfinishedTracesForDebug()).containsExactly("One", "Two").inOrder();
+
+        log.traceEnd();
+        assertThat(log.getUnfinishedTracesForDebug()).containsExactly("One").inOrder();
+
+        log.traceEnd();
+        assertThat(log.getUnfinishedTracesForDebug()).isEmpty();
+    }
+
+    @Test
+    public void testLogDuration() throws Exception {
+        TimingsTraceLog log = new TimingsTraceLog(TAG, TRACE_TAG_APP, 10);
+        log.logDuration("logro", 42);
+        verify((MockedVoidMethod) () -> Slog.d(eq(TAG), contains("logro took to complete: 42ms")));
+    }
+
+    @Test
+    public void testOneLevel() throws Exception {
+        TimingsTraceLog log = new TimingsTraceLog(TAG, TRACE_TAG_APP, 10);
+        log.traceBegin("test");
+        log.traceEnd();
+
+        verify((MockedVoidMethod) () -> Trace.traceBegin(TRACE_TAG_APP, "test"));
+        verify((MockedVoidMethod) () -> Trace.traceEnd(TRACE_TAG_APP));
+        verify((MockedVoidMethod) () -> Slog.d(eq(TAG), matches("test took to complete: \\dms")));
+    }
+
+    @Test
+    public void testMultipleLevels() throws Exception {
+        TimingsTraceLog log = new TimingsTraceLog(TAG, Trace.TRACE_TAG_APP, 10);
+        log.traceBegin("L1");
+        log.traceBegin("L2");
+        log.traceEnd();
+        log.traceEnd();
+
+        verify((MockedVoidMethod) () -> Trace.traceBegin(TRACE_TAG_APP, "L1"));
+        verify((MockedVoidMethod) () -> Trace.traceBegin(TRACE_TAG_APP, "L2"));
+        verify((MockedVoidMethod) () -> Trace.traceEnd(TRACE_TAG_APP), times(2)); // L1 and L2
+
+        verify((MockedVoidMethod) () -> Slog.d(eq(TAG), matches("L2 took to complete: \\d+ms")));
+        verify((MockedVoidMethod) () -> Slog.d(eq(TAG), matches("L1 took to complete: \\d+ms")));
+    }
+
+    @Test
+    public void testTooManyLevels() throws Exception {
+        TimingsTraceLog log = new TimingsTraceLog(TAG, Trace.TRACE_TAG_APP, 2);
+
+        log.traceBegin("L1"); // ok
+        log.traceBegin("L2"); // ok
+        log.traceBegin("L3"); // logging ignored ( > 2)
+
+        log.traceEnd();
+        log.traceEnd();
+        log.traceEnd();
+
+        verify((MockedVoidMethod) () -> Trace.traceBegin(TRACE_TAG_APP, "L1"));
+        verify((MockedVoidMethod) () -> Trace.traceBegin(TRACE_TAG_APP, "L2"));
+        verify((MockedVoidMethod) () -> Trace.traceBegin(TRACE_TAG_APP, "L3"));
+        verify((MockedVoidMethod) () -> Trace.traceEnd(TRACE_TAG_APP), times(3));
+
+        verify((MockedVoidMethod) () -> Slog.d(eq(TAG), matches("L2 took to complete: \\d+ms")));
+        verify((MockedVoidMethod) () -> Slog.d(eq(TAG), matches("L1 took to complete: \\d+ms")));
+        verify((MockedVoidMethod) () -> Slog.d(eq(TAG), matches("L3 took to complete: \\d+ms")),
+                never());
+
+        verify((MockedVoidMethod) () -> Slog.w(TAG, "not tracing duration of 'L3' "
+                + "because already reached 2 levels"));
+    }
+
+    @Test
+    public void testEndNoBegin() throws Exception {
+        TimingsTraceLog log = new TimingsTraceLog(TAG, TRACE_TAG_APP);
+        log.traceEnd();
+        verify((MockedVoidMethod) () -> Trace.traceEnd(TRACE_TAG_APP));
+        verify((MockedVoidMethod) () -> Slog.d(eq(TAG), anyString()), never());
+        verify((MockedVoidMethod) () -> Slog.w(TAG, "traceEnd called more times than traceBegin"));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index 1124220..4d7cf27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -33,6 +33,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.om.IOverlayManager;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.ApkAssets;
 import android.os.PatternMatcher;
@@ -69,6 +70,8 @@
     private static final String TAG = NavigationModeController.class.getSimpleName();
     private static final boolean DEBUG = false;
 
+    private static final int SYSTEM_APP_MASK =
+            ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
     static final String SHARED_PREFERENCES_NAME = "navigation_mode_controller_preferences";
     static final String PREFS_SWITCHED_FROM_GESTURE_NAV_KEY = "switched_from_gesture_nav";
 
@@ -315,6 +318,10 @@
             return;
         }
 
+        Log.d(TAG, "Switching system navigation to 3-button mode:"
+                + " defaultLauncher=" + getDefaultLauncherPackageName(mCurrentUserContext)
+                + " contextUser=" + mCurrentUserContext.getUserId());
+
         setModeOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, USER_CURRENT);
         showNotification(mCurrentUserContext, R.string.notification_content_system_nav_changed);
         mCurrentUserContext.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
@@ -355,9 +362,10 @@
         if (defaultLauncherPackageName == null) {
             return null;
         }
-        ComponentName recentsComponentName = ComponentName.unflattenFromString(
-                context.getString(com.android.internal.R.string.config_recentsComponentName));
-        return recentsComponentName.getPackageName().equals(defaultLauncherPackageName);
+        if (isSystemApp(context, defaultLauncherPackageName)) {
+            return true;
+        }
+        return false;
     }
 
     private String getDefaultLauncherPackageName(Context context) {
@@ -368,6 +376,17 @@
         return cn.getPackageName();
     }
 
+    /** Returns true if the app for the given package name is a system app for this device */
+    private boolean isSystemApp(Context context, String packageName) {
+        try {
+            ApplicationInfo ai = context.getPackageManager().getApplicationInfo(packageName,
+                    PackageManager.GET_META_DATA);
+            return ai != null && ((ai.flags & SYSTEM_APP_MASK) != 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            return false;
+        }
+    }
+
     private void showNotification(Context context, int resId) {
         final CharSequence message = context.getResources().getString(resId);
         if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 186fd37..64a8ce0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2430,7 +2430,6 @@
             onUnlockHintFinished();
             return;
         }
-        mUpdateMonitor.requestFaceAuth();
         super.startUnlockHintAnimation();
     }
 
@@ -2651,10 +2650,14 @@
         switch (mBarState) {
             case StatusBarState.KEYGUARD:
                 if (!mDozingOnDown) {
-                    mLockscreenGestureLogger.write(
-                            MetricsEvent.ACTION_LS_HINT,
-                            0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
-                    startUnlockHintAnimation();
+                    if (mKeyguardBypassController.getBypassEnabled()) {
+                        mUpdateMonitor.requestFaceAuth();
+                    } else {
+                        mLockscreenGestureLogger.write(
+                                MetricsEvent.ACTION_LS_HINT,
+                                0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+                        startUnlockHintAnimation();
+                    }
                 }
                 return true;
             case StatusBarState.SHADE_LOCKED:
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
index d6657f1..8415272 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java
@@ -31,6 +31,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.text.TextUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.IWindow;
@@ -83,6 +84,7 @@
             mInteractionConnections = new SparseArray<>();
     private final SparseArray<SparseArray<IBinder>> mWindowTokens = new SparseArray<>();
 
+    private final List<WindowInfo> mCachedWindowInfos = new ArrayList<>();
     private List<AccessibilityWindowInfo> mWindows;
 
     private RemoteAccessibilityConnection mPictureInPictureActionReplacingConnection;
@@ -173,25 +175,133 @@
     }
 
     /**
-     * Callbacks from from window manager when there's an accessibility change in windows.
+     * Callbacks from window manager when there's an accessibility change in windows.
      *
+     * @param forceSend Send the windows for accessibility even if they haven't changed.
      * @param windows The windows of current display for accessibility.
      */
     @Override
-    public void onWindowsForAccessibilityChanged(@NonNull List<WindowInfo> windows) {
+    public void onWindowsForAccessibilityChanged(boolean forceSend,
+            @NonNull List<WindowInfo> windows) {
         synchronized (mLock) {
             if (DEBUG) {
                 Slog.i(LOG_TAG, "Windows changed: " + windows);
             }
 
-            // Let the policy update the focused and active windows.
-            updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(), windows);
-
-            // Someone may be waiting for the windows - advertise it.
-            mLock.notifyAll();
+            if (shouldUpdateWindowsLocked(forceSend, windows)) {
+                cacheWindows(windows);
+                // Let the policy update the focused and active windows.
+                updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(), windows);
+                // Someone may be waiting for the windows - advertise it.
+                mLock.notifyAll();
+            }
         }
     }
 
+    private boolean shouldUpdateWindowsLocked(boolean forceSend,
+            @NonNull List<WindowInfo> windows) {
+        if (forceSend) {
+            return true;
+        }
+
+        final int windowCount = windows.size();
+        // We computed the windows and if they changed notify the client.
+        if (mCachedWindowInfos.size() != windowCount) {
+            // Different size means something changed.
+            return true;
+        } else if (!mCachedWindowInfos.isEmpty() || !windows.isEmpty()) {
+            // Since we always traverse windows from high to low layer
+            // the old and new windows at the same index should be the
+            // same, otherwise something changed.
+            for (int i = 0; i < windowCount; i++) {
+                WindowInfo oldWindow = mCachedWindowInfos.get(i);
+                WindowInfo newWindow = windows.get(i);
+                // We do not care for layer changes given the window
+                // order does not change. This brings no new information
+                // to the clients.
+                if (windowChangedNoLayer(oldWindow, newWindow)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    private void cacheWindows(List<WindowInfo> windows) {
+        final int oldWindowCount = mCachedWindowInfos.size();
+        for (int i = oldWindowCount - 1; i >= 0; i--) {
+            mCachedWindowInfos.remove(i).recycle();
+        }
+        final int newWindowCount = windows.size();
+        for (int i = 0; i < newWindowCount; i++) {
+            WindowInfo newWindow = windows.get(i);
+            mCachedWindowInfos.add(WindowInfo.obtain(newWindow));
+        }
+    }
+
+    private boolean windowChangedNoLayer(WindowInfo oldWindow, WindowInfo newWindow) {
+        if (oldWindow == newWindow) {
+            return false;
+        }
+        if (oldWindow == null) {
+            return true;
+        }
+        if (newWindow == null) {
+            return true;
+        }
+        if (oldWindow.type != newWindow.type) {
+            return true;
+        }
+        if (oldWindow.focused != newWindow.focused) {
+            return true;
+        }
+        if (oldWindow.token == null) {
+            if (newWindow.token != null) {
+                return true;
+            }
+        } else if (!oldWindow.token.equals(newWindow.token)) {
+            return true;
+        }
+        if (oldWindow.parentToken == null) {
+            if (newWindow.parentToken != null) {
+                return true;
+            }
+        } else if (!oldWindow.parentToken.equals(newWindow.parentToken)) {
+            return true;
+        }
+        if (oldWindow.activityToken == null) {
+            if (newWindow.activityToken != null) {
+                return true;
+            }
+        } else if (!oldWindow.activityToken.equals(newWindow.activityToken)) {
+            return true;
+        }
+        if (!oldWindow.boundsInScreen.equals(newWindow.boundsInScreen)) {
+            return true;
+        }
+        if (oldWindow.childTokens != null && newWindow.childTokens != null
+                && !oldWindow.childTokens.equals(newWindow.childTokens)) {
+            return true;
+        }
+        if (!TextUtils.equals(oldWindow.title, newWindow.title)) {
+            return true;
+        }
+        if (oldWindow.accessibilityIdOfAnchor != newWindow.accessibilityIdOfAnchor) {
+            return true;
+        }
+        if (oldWindow.inPictureInPicture != newWindow.inPictureInPicture) {
+            return true;
+        }
+        if (oldWindow.hasFlagWatchOutsideTouch != newWindow.hasFlagWatchOutsideTouch) {
+            return true;
+        }
+        if (oldWindow.displayId != newWindow.displayId) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Start tracking windows changes from window manager.
      */
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 6eb9f0c..0748279 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
@@ -193,7 +194,7 @@
         // with other system events, any duration calculations should be done use startTime so as
         // not to be affected by discontinuities created by RTC adjustments.
         public final long startTimeDebug;
-        public final int usageHint;
+        public final AudioAttributes attrs;
         public final int uid;
         public final String opPkg;
         public final String reason;
@@ -206,12 +207,12 @@
         public VibrationEffect originalEffect;
 
         private Vibration(IBinder token, VibrationEffect effect,
-                int usageHint, int uid, String opPkg, String reason) {
+                AudioAttributes attrs, int uid, String opPkg, String reason) {
             this.token = token;
             this.effect = effect;
             this.startTime = SystemClock.elapsedRealtime();
             this.startTimeDebug = System.currentTimeMillis();
-            this.usageHint = usageHint;
+            this.attrs = attrs;
             this.uid = uid;
             this.opPkg = opPkg;
             this.reason = reason;
@@ -231,7 +232,7 @@
         }
 
         public boolean isHapticFeedback() {
-            if (VibratorService.this.isHapticFeedback(usageHint)) {
+            if (VibratorService.this.isHapticFeedback(attrs.getUsage())) {
                 return true;
             }
             if (effect instanceof VibrationEffect.Prebaked) {
@@ -256,15 +257,15 @@
         }
 
         public boolean isNotification() {
-            return VibratorService.this.isNotification(usageHint);
+            return VibratorService.this.isNotification(attrs.getUsage());
         }
 
         public boolean isRingtone() {
-            return VibratorService.this.isRingtone(usageHint);
+            return VibratorService.this.isRingtone(attrs.getUsage());
         }
 
         public boolean isAlarm() {
-            return VibratorService.this.isAlarm(usageHint);
+            return VibratorService.this.isAlarm(attrs.getUsage());
         }
 
         public boolean isFromSystem() {
@@ -273,7 +274,7 @@
 
         public VibrationInfo toInfo() {
             return new VibrationInfo(
-                    startTimeDebug, effect, originalEffect, usageHint, uid, opPkg, reason);
+                    startTimeDebug, effect, originalEffect, attrs, uid, opPkg, reason);
         }
     }
 
@@ -281,18 +282,18 @@
         private final long mStartTimeDebug;
         private final VibrationEffect mEffect;
         private final VibrationEffect mOriginalEffect;
-        private final int mUsageHint;
+        private final AudioAttributes mAttrs;
         private final int mUid;
         private final String mOpPkg;
         private final String mReason;
 
         public VibrationInfo(long startTimeDebug, VibrationEffect effect,
-                VibrationEffect originalEffect, int usageHint, int uid,
+                VibrationEffect originalEffect, AudioAttributes attrs, int uid,
                 String opPkg, String reason) {
             mStartTimeDebug = startTimeDebug;
             mEffect = effect;
             mOriginalEffect = originalEffect;
-            mUsageHint = usageHint;
+            mAttrs = attrs;
             mUid = uid;
             mOpPkg = opPkg;
             mReason = reason;
@@ -307,8 +308,8 @@
                     .append(mEffect)
                     .append(", originalEffect: ")
                     .append(mOriginalEffect)
-                    .append(", usageHint: ")
-                    .append(mUsageHint)
+                    .append(", attrs: ")
+                    .append(mAttrs)
                     .append(", uid: ")
                     .append(mUid)
                     .append(", opPkg: ")
@@ -549,12 +550,11 @@
     }
 
     @Override // Binder call
-    public void vibrate(int uid, String opPkg, VibrationEffect effect, int usageHint, String reason,
-            IBinder token) {
+    public void vibrate(int uid, String opPkg, VibrationEffect effect,
+            @Nullable AudioAttributes attrs, String reason, IBinder token) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
         try {
-            if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.VIBRATE)
-                    != PackageManager.PERMISSION_GRANTED) {
+            if (!hasPermission(android.Manifest.permission.VIBRATE)) {
                 throw new SecurityException("Requires VIBRATE permission");
             }
             if (token == null) {
@@ -566,6 +566,22 @@
                 return;
             }
 
+            if (attrs == null) {
+                attrs = new AudioAttributes.Builder()
+                        .setUsage(AudioAttributes.USAGE_UNKNOWN)
+                        .build();
+            }
+
+            if (shouldBypassDnd(attrs)) {
+                if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+                        || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+                        || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
+                    final int flags = attrs.getAllFlags()
+                            & ~AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+                    attrs = new AudioAttributes.Builder(attrs).replaceFlags(flags).build();
+                }
+            }
+
             // If our current vibration is longer than the new vibration and is the same amplitude,
             // then just let the current one finish.
             synchronized (mLock) {
@@ -608,13 +624,13 @@
                     return;
                 }
 
-                Vibration vib = new Vibration(token, effect, usageHint, uid, opPkg, reason);
+                Vibration vib = new Vibration(token, effect, attrs, uid, opPkg, reason);
                 if (mProcStatesCache.get(uid, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND)
                         > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
                         && !vib.isNotification() && !vib.isRingtone() && !vib.isAlarm()) {
                     Slog.e(TAG, "Ignoring incoming vibration as process with"
-                            + " uid = " + uid + " is background,"
-                            + " usage = " + AudioAttributes.usageToString(vib.usageHint));
+                            + " uid= " + uid + " is background,"
+                            + " attrs= " + vib.attrs);
                     return;
                 }
                 linkVibration(vib);
@@ -632,6 +648,11 @@
         }
     }
 
+    private boolean hasPermission(String permission) {
+        return mContext.checkCallingOrSelfPermission(permission)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     private static boolean isRepeatingVibration(VibrationEffect effect) {
         return effect.getDuration() == Long.MAX_VALUE;
     }
@@ -760,14 +781,14 @@
             if (vib.effect instanceof VibrationEffect.OneShot) {
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                 VibrationEffect.OneShot oneShot = (VibrationEffect.OneShot) vib.effect;
-                doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib.uid, vib.usageHint);
+                doVibratorOn(oneShot.getDuration(), oneShot.getAmplitude(), vib.uid, vib.attrs);
                 mH.postDelayed(mVibrationEndRunnable, oneShot.getDuration());
             } else if (vib.effect instanceof VibrationEffect.Waveform) {
                 // mThread better be null here. doCancelVibrate should always be
                 // called before startNextVibrationLocked or startVibrationLocked.
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
                 VibrationEffect.Waveform waveform = (VibrationEffect.Waveform) vib.effect;
-                mThread = new VibrateThread(waveform, vib.uid, vib.usageHint);
+                mThread = new VibrateThread(waveform, vib.uid, vib.attrs);
                 mThread.start();
             } else if (vib.effect instanceof VibrationEffect.Prebaked) {
                 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIBRATOR, "vibration", 0);
@@ -788,13 +809,14 @@
             return true;
         }
 
-        if (vib.usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
+        if (vib.attrs.getUsage() == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
             return true;
         }
 
-        if (vib.usageHint == AudioAttributes.USAGE_ALARM ||
-                vib.usageHint == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY ||
-                vib.usageHint == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {
+        if (vib.attrs.getUsage() == AudioAttributes.USAGE_ALARM
+                || vib.attrs.getUsage() == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY
+                || vib.attrs.getUsage()
+                    == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {
             return true;
         }
 
@@ -887,12 +909,24 @@
         }
     }
 
+    private static boolean shouldBypassDnd(AudioAttributes attrs) {
+        return (attrs.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0;
+    }
+
     private int getAppOpMode(Vibration vib) {
         int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
-                vib.usageHint, vib.uid, vib.opPkg);
+                vib.attrs.getUsage(), vib.uid, vib.opPkg);
         if (mode == AppOpsManager.MODE_ALLOWED) {
             mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, vib.uid, vib.opPkg);
         }
+
+        if (mode == AppOpsManager.MODE_IGNORED && shouldBypassDnd(vib.attrs)) {
+            // If we're just ignoring the vibration op then this is set by DND and we should ignore
+            // if we're asked to bypass. AppOps won't be able to record this operation, so make
+            // sure we at least note it in the logs for debugging.
+            Slog.d(TAG, "Bypassing DND for vibration: " + vib);
+            mode = AppOpsManager.MODE_ALLOWED;
+        }
         return mode;
     }
 
@@ -1032,7 +1066,7 @@
         return vibratorExists();
     }
 
-    private void doVibratorOn(long millis, int amplitude, int uid, int usageHint) {
+    private void doVibratorOn(long millis, int amplitude, int uid, AudioAttributes attrs) {
         Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
         try {
             synchronized (mInputDeviceVibrators) {
@@ -1046,10 +1080,8 @@
                 noteVibratorOnLocked(uid, millis);
                 final int vibratorCount = mInputDeviceVibrators.size();
                 if (vibratorCount != 0) {
-                    final AudioAttributes attributes =
-                            new AudioAttributes.Builder().setUsage(usageHint).build();
                     for (int i = 0; i < vibratorCount; i++) {
-                        mInputDeviceVibrators.get(i).vibrate(millis, attributes);
+                        mInputDeviceVibrators.get(i).vibrate(millis, attrs);
                     }
                 } else {
                     // Note: ordering is important here! Many haptic drivers will reset their
@@ -1118,7 +1150,7 @@
                 Slog.w(TAG, "Failed to play prebaked effect, no fallback");
                 return 0;
             }
-            Vibration fallbackVib = new Vibration(vib.token, effect, vib.usageHint, vib.uid,
+            Vibration fallbackVib = new Vibration(vib.token, effect, vib.attrs, vib.uid,
                     vib.opPkg, vib.reason + " (fallback)");
             final int intensity = getCurrentIntensityLocked(fallbackVib);
             linkVibration(fallbackVib);
@@ -1213,14 +1245,14 @@
     private class VibrateThread extends Thread {
         private final VibrationEffect.Waveform mWaveform;
         private final int mUid;
-        private final int mUsageHint;
+        private final AudioAttributes mAttrs;
 
         private boolean mForceStop;
 
-        VibrateThread(VibrationEffect.Waveform waveform, int uid, int usageHint) {
+        VibrateThread(VibrationEffect.Waveform waveform, int uid, AudioAttributes attrs) {
             mWaveform = waveform;
             mUid = uid;
-            mUsageHint = usageHint;
+            mAttrs = attrs;
             mTmpWorkSource.set(uid);
             mWakeLock.setWorkSource(mTmpWorkSource);
         }
@@ -1295,7 +1327,7 @@
                                     // appropriate intervals.
                                     onDuration = getTotalOnDuration(timings, amplitudes, index - 1,
                                             repeat);
-                                    doVibratorOn(onDuration, amplitude, mUid, mUsageHint);
+                                    doVibratorOn(onDuration, amplitude, mUid, mAttrs);
                                 } else {
                                     doVibratorSetAmplitude(amplitude);
                                 }
@@ -1612,8 +1644,9 @@
 
                 VibrationEffect effect =
                         VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
-                vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
-                        "Shell Command", mToken);
+                AudioAttributes attrs = createAudioAttributes(commonOptions);
+                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
+                        mToken);
                 return 0;
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -1672,8 +1705,9 @@
                             amplitudesList.stream().mapToInt(Integer::intValue).toArray();
                     effect = VibrationEffect.createWaveform(timings, amplitudes, repeat);
                 }
-                vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
-                        "Shell Command", mToken);
+                AudioAttributes attrs = createAudioAttributes(commonOptions);
+                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
+                        mToken);
                 return 0;
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
@@ -1703,14 +1737,25 @@
 
                 VibrationEffect effect =
                         VibrationEffect.get(id, false);
-                vibrate(Binder.getCallingUid(), description, effect, AudioAttributes.USAGE_UNKNOWN,
-                        "Shell Command", mToken);
+                AudioAttributes attrs = createAudioAttributes(commonOptions);
+                vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
+                        mToken);
                 return 0;
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_VIBRATOR);
             }
         }
 
+        private AudioAttributes createAudioAttributes(CommonOptions commonOptions) {
+            final int flags = commonOptions.force
+                    ? AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
+                    : 0;
+            return new AudioAttributes.Builder()
+                    .setUsage(AudioAttributes.USAGE_UNKNOWN)
+                    .setFlags(flags)
+                    .build();
+        }
+
         @Override
         public void onHelp() {
             try (PrintWriter pw = getOutPrintWriter();) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index f451a09..4222fc1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -272,6 +272,7 @@
 import android.os.storage.StorageManager;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
+import android.provider.DeviceConfig.Properties;
 import android.server.ServerProtoEnums;
 import android.sysprop.VoldProperties;
 import android.text.TextUtils;
@@ -356,6 +357,7 @@
 import com.android.server.utils.PriorityDump;
 import com.android.server.utils.TimingsTraceAndSlog;
 import com.android.server.vr.VrManagerInternal;
+import com.android.server.wm.ActivityMetricsLaunchObserver;
 import com.android.server.wm.ActivityServiceConnectionsHolder;
 import com.android.server.wm.ActivityTaskManagerInternal;
 import com.android.server.wm.ActivityTaskManagerService;
@@ -392,6 +394,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BiFunction;
 
@@ -861,6 +864,51 @@
      */
     final ArrayList<ProcessRecord> mPendingPssProcesses = new ArrayList<ProcessRecord>();
 
+    /**
+     * Depth of overlapping activity-start PSS deferral notes
+     */
+    private final AtomicInteger mActivityStartingNesting = new AtomicInteger(0);
+
+    private final ActivityMetricsLaunchObserver mActivityLaunchObserver =
+            new ActivityMetricsLaunchObserver() {
+        @Override
+        public void onActivityLaunched(byte[] activity, int temperature) {
+            // This is safe to force to the head of the queue because it relies only
+            // on refcounting to track begin/end of deferrals, not on actual
+            // message ordering.  We don't care *what* activity is being
+            // launched; only that we're doing so.
+            if (mPssDeferralTime > 0) {
+                final Message msg = mBgHandler.obtainMessage(DEFER_PSS_MSG);
+                mBgHandler.sendMessageAtFrontOfQueue(msg);
+            }
+        }
+
+        // The other observer methods are unused
+        @Override
+        public void onIntentStarted(Intent intent) {
+        }
+
+        @Override
+        public void onIntentFailed() {
+        }
+
+        @Override
+        public void onActivityLaunchCancelled(byte[] abortingActivity) {
+        }
+
+        @Override
+        public void onActivityLaunchFinished(byte[] finalActivity) {
+        }
+    };
+
+    /**
+     * How long we defer PSS gathering while activities are starting, in milliseconds.
+     * This is adjustable via DeviceConfig.  If it is zero or negative, no PSS deferral
+     * is done.
+     */
+    private volatile long mPssDeferralTime = 0;
+    private static final String ACTIVITY_START_PSS_DEFER_CONFIG = "activity_start_pss_defer";
+
     private boolean mBinderTransactionTrackingEnabled = false;
 
     /**
@@ -874,6 +922,20 @@
      */
     boolean mFullPssPending = false;
 
+    /**
+     * Observe DeviceConfig changes to the PSS calculation interval
+     */
+    private final DeviceConfig.OnPropertiesChangedListener mPssDelayConfigListener =
+            new DeviceConfig.OnPropertiesChangedListener() {
+                @Override
+                public void onPropertiesChanged(Properties properties) {
+                    mPssDeferralTime = properties.getLong(ACTIVITY_START_PSS_DEFER_CONFIG, 0);
+                    if (DEBUG_PSS) {
+                        Slog.d(TAG_PSS, "Activity-start PSS delay now "
+                                + mPssDeferralTime + " ms");
+                    }
+                }
+            };
 
     /**
      * This is for verifying the UID report flow.
@@ -1838,6 +1900,8 @@
     }
 
     static final int COLLECT_PSS_BG_MSG = 1;
+    static final int DEFER_PSS_MSG = 2;
+    static final int STOP_DEFERRING_PSS_MSG = 3;
 
     final Handler mBgHandler = new Handler(BackgroundThread.getHandler().getLooper()) {
         @Override
@@ -1945,6 +2009,30 @@
                     }
                 } while (true);
             }
+
+            case DEFER_PSS_MSG: {
+                deferPssForActivityStart();
+            } break;
+
+            case STOP_DEFERRING_PSS_MSG: {
+                final int nesting = mActivityStartingNesting.decrementAndGet();
+                if (nesting <= 0) {
+                    if (DEBUG_PSS) {
+                        Slog.d(TAG_PSS, "PSS activity start deferral interval ended; now "
+                                + nesting);
+                    }
+                    if (nesting < 0) {
+                        Slog.wtf(TAG, "Activity start nesting undercount!");
+                        mActivityStartingNesting.incrementAndGet();
+                    }
+                } else {
+                    if (DEBUG_PSS) {
+                        Slog.d(TAG_PSS, "Still deferring PSS, nesting=" + nesting);
+                    }
+                }
+            }
+            break;
+
             }
         }
     };
@@ -8832,6 +8920,12 @@
                 NETWORK_ACCESS_TIMEOUT_MS, NETWORK_ACCESS_TIMEOUT_DEFAULT_MS);
         mHiddenApiBlacklist.registerObserver();
 
+        final long pssDeferralMs = DeviceConfig.getLong(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                ACTIVITY_START_PSS_DEFER_CONFIG, 0L);
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+                ActivityThread.currentApplication().getMainExecutor(),
+                mPssDelayConfigListener);
+
         synchronized (this) {
             mDebugApp = mOrigDebugApp = debugApp;
             mWaitForDebugger = mOrigWaitForDebugger = waitForDebugger;
@@ -8848,6 +8942,7 @@
                     com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
 
             mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
+            mPssDeferralTime = pssDeferralMs;
         }
     }
 
@@ -8921,6 +9016,10 @@
         mAtmInternal.updateTopComponentForFactoryTest();
         t.traceEnd();
 
+        t.traceBegin("registerActivityLaunchObserver");
+        mAtmInternal.getLaunchObserverRegistry().registerLaunchObserver(mActivityLaunchObserver);
+        t.traceEnd();
+
         t.traceBegin("watchDeviceProvisioning");
         watchDeviceProvisioning(mContext);
         t.traceEnd();
@@ -16177,7 +16276,13 @@
             return false;
         }
         if (mPendingPssProcesses.size() == 0) {
-            mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG);
+            final long deferral = (mPssDeferralTime > 0 && mActivityStartingNesting.get() > 0)
+                    ? mPssDeferralTime : 0;
+            if (DEBUG_PSS && deferral > 0) {
+                Slog.d(TAG_PSS, "requestPssLocked() deferring PSS request by "
+                        + deferral + " ms");
+            }
+            mBgHandler.sendEmptyMessageDelayed(COLLECT_PSS_BG_MSG, deferral);
         }
         if (DEBUG_PSS) Slog.d(TAG_PSS, "Requesting pss of: " + proc);
         proc.pssProcState = procState;
@@ -16187,6 +16292,30 @@
     }
 
     /**
+     * Re-defer a posted PSS collection pass, if one exists.  Assumes deferral is
+     * currently active policy when called.
+     */
+    private void deferPssIfNeededLocked() {
+        if (mPendingPssProcesses.size() > 0) {
+            mBgHandler.removeMessages(COLLECT_PSS_BG_MSG);
+            mBgHandler.sendEmptyMessageDelayed(COLLECT_PSS_BG_MSG, mPssDeferralTime);
+        }
+    }
+
+    private void deferPssForActivityStart() {
+        synchronized (ActivityManagerService.this) {
+            if (mPssDeferralTime > 0) {
+                if (DEBUG_PSS) {
+                    Slog.d(TAG_PSS, "Deferring PSS collection for activity start");
+                }
+                deferPssIfNeededLocked();
+                mActivityStartingNesting.getAndIncrement();
+                mBgHandler.sendEmptyMessageDelayed(STOP_DEFERRING_PSS_MSG, mPssDeferralTime);
+            }
+        }
+    }
+
+    /**
      * Schedule PSS collection of all processes.
      */
     void requestPssAllProcsLocked(long now, boolean always, boolean memLowered) {
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 6b7187e..a2891e9 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -43,9 +43,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.text.TextUtils;
 import android.util.ArraySet;
-import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TypedValue;
@@ -1036,12 +1034,9 @@
 
         private static final boolean DEBUG = false;
 
-        private final SparseArray<WindowState> mTempWindowStates =
-                new SparseArray<WindowState>();
+        private final SparseArray<WindowState> mTempWindowStates = new SparseArray<>();
 
-        private final List<WindowInfo> mOldWindows = new ArrayList<WindowInfo>();
-
-        private final Set<IBinder> mTempBinderSet = new ArraySet<IBinder>();
+        private final Set<IBinder> mTempBinderSet = new ArraySet<>();
 
         private final RectF mTempRectF = new RectF();
 
@@ -1098,8 +1093,7 @@
                 Slog.i(LOG_TAG, "computeChangedWindows()");
             }
 
-            boolean windowsChanged = false;
-            List<WindowInfo> windows = new ArrayList<WindowInfo>();
+            List<WindowInfo> windows = new ArrayList<>();
 
             synchronized (mService.mGlobalLock) {
                 // Do not send the windows if there is no current focus as
@@ -1169,46 +1163,9 @@
 
                 visibleWindows.clear();
                 addedWindows.clear();
-
-                if (!forceSend) {
-                    // We computed the windows and if they changed notify the client.
-                    if (mOldWindows.size() != windows.size()) {
-                        // Different size means something changed.
-                        windowsChanged = true;
-                    } else if (!mOldWindows.isEmpty() || !windows.isEmpty()) {
-                        // Since we always traverse windows from high to low layer
-                        // the old and new windows at the same index should be the
-                        // same, otherwise something changed.
-                        for (int i = 0; i < windowCount; i++) {
-                            WindowInfo oldWindow = mOldWindows.get(i);
-                            WindowInfo newWindow = windows.get(i);
-                            // We do not care for layer changes given the window
-                            // order does not change. This brings no new information
-                            // to the clients.
-                            if (windowChangedNoLayer(oldWindow, newWindow)) {
-                                windowsChanged = true;
-                                break;
-                            }
-                        }
-                    }
-                }
-
-                if (forceSend || windowsChanged) {
-                    cacheWindows(windows);
-                }
             }
 
-            // Now we do not hold the lock, so send the windows over.
-            if (forceSend || windowsChanged) {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "Windows changed or force sending:" + windows);
-                }
-                mCallback.onWindowsForAccessibilityChanged(windows);
-            } else {
-                if (DEBUG) {
-                    Log.i(LOG_TAG, "No windows changed.");
-                }
-            }
+            mCallback.onWindowsForAccessibilityChanged(forceSend, windows);
 
             // Recycle the windows as we do not need them.
             clearAndRecycleWindows(windows);
@@ -1313,67 +1270,6 @@
             tokenOut.add(window.token);
         }
 
-        private void cacheWindows(List<WindowInfo> windows) {
-            final int oldWindowCount = mOldWindows.size();
-            for (int i = oldWindowCount - 1; i >= 0; i--) {
-                mOldWindows.remove(i).recycle();
-            }
-            final int newWindowCount = windows.size();
-            for (int i = 0; i < newWindowCount; i++) {
-                WindowInfo newWindow = windows.get(i);
-                mOldWindows.add(WindowInfo.obtain(newWindow));
-            }
-        }
-
-        private boolean windowChangedNoLayer(WindowInfo oldWindow, WindowInfo newWindow) {
-            if (oldWindow == newWindow) {
-                return false;
-            }
-            if (oldWindow == null) {
-                return true;
-            }
-            if (newWindow == null) {
-                return true;
-            }
-            if (oldWindow.type != newWindow.type) {
-                return true;
-            }
-            if (oldWindow.focused != newWindow.focused) {
-                return true;
-            }
-            if (oldWindow.token == null) {
-                if (newWindow.token != null) {
-                    return true;
-                }
-            } else if (!oldWindow.token.equals(newWindow.token)) {
-                return true;
-            }
-            if (oldWindow.parentToken == null) {
-                if (newWindow.parentToken != null) {
-                    return true;
-                }
-            } else if (!oldWindow.parentToken.equals(newWindow.parentToken)) {
-                return true;
-            }
-            if (!oldWindow.boundsInScreen.equals(newWindow.boundsInScreen)) {
-                return true;
-            }
-            if (oldWindow.childTokens != null && newWindow.childTokens != null
-                    && !oldWindow.childTokens.equals(newWindow.childTokens)) {
-                return true;
-            }
-            if (!TextUtils.equals(oldWindow.title, newWindow.title)) {
-                return true;
-            }
-            if (oldWindow.accessibilityIdOfAnchor != newWindow.accessibilityIdOfAnchor) {
-                return true;
-            }
-            if (oldWindow.displayId != newWindow.displayId) {
-                return true;
-            }
-            return false;
-        }
-
         private static void clearAndRecycleWindows(List<WindowInfo> windows) {
             final int windowCount = windows.size();
             for (int i = windowCount - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index deae923..4a9a3f7 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -79,7 +79,6 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
 import static com.android.server.wm.WindowManagerService.logWithStack;
 import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
-import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 
@@ -541,14 +540,6 @@
                 // If the app was already visible, don't reset the waitingToShow state.
                 if (isHidden()) {
                     waitingToShow = true;
-
-                    // Let's reset the draw state in order to prevent the starting window to be
-                    // immediately dismissed when the app still has the surface.
-                    forAllWindows(w -> {
-                            if (w.mWinAnimator.mDrawState == HAS_DRAWN) {
-                                w.mWinAnimator.resetDrawState();
-                            }
-                        },  true /* traverseTopToBottom */);
                 }
             }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 40bec14..6910ce9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -51,9 +51,10 @@
         /**
          * Called when the windows for accessibility changed.
          *
+         * @param forceSend Send the windows for accessibility even if they haven't changed.
          * @param windows The windows for accessibility.
          */
-        public void onWindowsForAccessibilityChanged(List<WindowInfo> windows);
+        void onWindowsForAccessibilityChanged(boolean forceSend, List<WindowInfo> windows);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 264eeda..2bf24f6 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7694,7 +7694,7 @@
 
     private void onPointerDownOutsideFocusLocked(IBinder touchedToken) {
         final WindowState touchedWindow = windowForClientLocked(null, touchedToken, false);
-        if (touchedWindow == null) {
+        if (touchedWindow == null || !touchedWindow.canReceiveKeys()) {
             return;
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 04ca40e..22408cc 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -70,6 +70,8 @@
  */
 public class AccessibilityWindowManagerTest {
     private static final String PACKAGE_NAME = "com.android.server.accessibility";
+    private static final boolean FORCE_SEND = true;
+    private static final boolean SEND_ON_WINDOW_CHANGES = false;
     private static final int USER_SYSTEM_ID = UserHandle.USER_SYSTEM;
     private static final int NUM_GLOBAL_WINDOWS = 4;
     private static final int NUM_APP_WINDOWS = 4;
@@ -122,7 +124,7 @@
         mWindowInfos.get(DEFAULT_FOCUSED_INDEX).focused = true;
         // Turn on windows tracking, and update window info
         mA11yWindowManager.startTrackingWindows();
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(FORCE_SEND, mWindowInfos);
         assertEquals(mA11yWindowManager.getWindowListLocked().size(),
                 mWindowInfos.size());
 
@@ -169,16 +171,16 @@
     @Test
     public void onWindowsChanged_duringTouchInteractAndFocusChange_shouldChangeActiveWindow() {
         final int activeWindowId = mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID);
-        WindowInfo focuedWindowInfo = mWindowInfos.get(DEFAULT_FOCUSED_INDEX);
+        WindowInfo focusedWindowInfo = mWindowInfos.get(DEFAULT_FOCUSED_INDEX);
         assertEquals(activeWindowId, mA11yWindowManager.findWindowIdLocked(
-                USER_SYSTEM_ID, focuedWindowInfo.token));
+                USER_SYSTEM_ID, focusedWindowInfo.token));
 
-        focuedWindowInfo.focused = false;
-        focuedWindowInfo = mWindowInfos.get(DEFAULT_FOCUSED_INDEX + 1);
-        focuedWindowInfo.focused = true;
+        focusedWindowInfo.focused = false;
+        focusedWindowInfo = mWindowInfos.get(DEFAULT_FOCUSED_INDEX + 1);
+        focusedWindowInfo.focused = true;
 
         mA11yWindowManager.onTouchInteractionStart();
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
         assertNotEquals(activeWindowId, mA11yWindowManager.getActiveWindowId(USER_SYSTEM_ID));
     }
 
@@ -208,6 +210,52 @@
     }
 
     @Test
+    public void onWindowsChangedAndForceSend_shouldUpdateWindows() {
+        final WindowInfo windowInfo = mWindowInfos.get(0);
+        final int correctLayer = mA11yWindowManager.getWindowListLocked().get(0).getLayer();
+        windowInfo.layer += 1;
+
+        mA11yWindowManager.onWindowsForAccessibilityChanged(FORCE_SEND, mWindowInfos);
+        assertNotEquals(correctLayer, mA11yWindowManager.getWindowListLocked().get(0).getLayer());
+    }
+
+    @Test
+    public void onWindowsChangedNoForceSend_layerChanged_shouldNotUpdateWindows() {
+        final WindowInfo windowInfo = mWindowInfos.get(0);
+        final int correctLayer = mA11yWindowManager.getWindowListLocked().get(0).getLayer();
+        windowInfo.layer += 1;
+
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
+        assertEquals(correctLayer, mA11yWindowManager.getWindowListLocked().get(0).getLayer());
+    }
+
+    @Test
+    public void onWindowsChangedNoForceSend_windowChanged_shouldUpdateWindows()
+            throws RemoteException {
+        final AccessibilityWindowInfo oldWindow = mA11yWindowManager.getWindowListLocked().get(0);
+        final IWindow token = addAccessibilityInteractionConnection(true);
+        final WindowInfo windowInfo = WindowInfo.obtain();
+        windowInfo.type = AccessibilityWindowInfo.TYPE_APPLICATION;
+        windowInfo.token = token.asBinder();
+        windowInfo.layer = 0;
+        windowInfo.boundsInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+        mWindowInfos.set(0, windowInfo);
+
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
+        assertNotEquals(oldWindow, mA11yWindowManager.getWindowListLocked().get(0));
+    }
+
+    @Test
+    public void onWindowsChangedNoForceSend_focusChanged_shouldUpdateWindows() {
+        final WindowInfo focusedWindowInfo = mWindowInfos.get(DEFAULT_FOCUSED_INDEX);
+        final WindowInfo windowInfo = mWindowInfos.get(0);
+        focusedWindowInfo.focused = false;
+        windowInfo.focused = true;
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
+        assertTrue(mA11yWindowManager.getWindowListLocked().get(0).isFocused());
+    }
+
+    @Test
     public void removeAccessibilityInteractionConnection_byWindowToken_shouldRemoved() {
         for (int i = 0; i < NUM_OF_WINDOWS; i++) {
             final int windowId = mA11yWindowTokens.keyAt(i);
@@ -264,7 +312,7 @@
         windowInfo = mWindowInfos.get(1);
         windowInfo.boundsInScreen.set(0, SCREEN_HEIGHT / 2,
                 SCREEN_WIDTH, SCREEN_HEIGHT);
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
 
         final List<AccessibilityWindowInfo> a11yWindows = mA11yWindowManager.getWindowListLocked();
         final Region outBounds = new Region();
@@ -291,7 +339,7 @@
         windowInfo = mWindowInfos.get(1);
         windowInfo.boundsInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
 
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
         final List<AccessibilityWindowInfo> a11yWindows = mA11yWindowManager.getWindowListLocked();
         final Region outBounds = new Region();
         int windowId = a11yWindows.get(1).getId();
@@ -309,7 +357,7 @@
         windowInfo.boundsInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
         windowInfo = mWindowInfos.get(1);
         windowInfo.boundsInScreen.set(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
 
         final List<AccessibilityWindowInfo> a11yWindows = mA11yWindowManager.getWindowListLocked();
         final Region outBounds = new Region();
@@ -498,7 +546,7 @@
     public void getPictureInPictureWindow_shouldNotNull() {
         assertNull(mA11yWindowManager.getPictureInPictureWindow());
         mWindowInfos.get(1).inPictureInPicture = true;
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
 
         assertNotNull(mA11yWindowManager.getPictureInPictureWindow());
     }
@@ -511,7 +559,7 @@
                 mA11yWindowManager.getConnectionLocked(
                         USER_SYSTEM_ID, outsideWindowId).getRemote();
         mWindowInfos.get(0).hasFlagWatchOutsideTouch = true;
-        mA11yWindowManager.onWindowsForAccessibilityChanged(mWindowInfos);
+        mA11yWindowManager.onWindowsForAccessibilityChanged(SEND_ON_WINDOW_CHANGES, mWindowInfos);
 
         mA11yWindowManager.notifyOutsideTouch(USER_SYSTEM_ID, targetWindowId);
         verify(mockRemoteConnection).notifyOutsideTouch();
diff --git a/startop/scripts/app_startup/run_app_with_prefetch b/startop/scripts/app_startup/run_app_with_prefetch
index 643df1b..92a31c3 100755
--- a/startop/scripts/app_startup/run_app_with_prefetch
+++ b/startop/scripts/app_startup/run_app_with_prefetch
@@ -101,6 +101,15 @@
     esac
     shift
   done
+
+  if [[ $when == "aot" ]]; then
+    # TODO: re-implement aot later for experimenting.
+    echo "Error: --when $when is unsupported" >&2
+    exit 1
+  elif [[ $when != "jit" ]]; then
+    echo "Error: --when must be one of (aot jit)." >&2
+    exit 1
+  fi
 }
 
 echo_to_output_file() {
@@ -212,6 +221,12 @@
   local the_when="$1" # user: aot, jit
   local the_mode="$2" # warm, cold, fadvise, mlock, etc.
 
+  # iorapd readahead for jit+(mlock/fadvise)
+  if [[ $the_when == "jit" && $the_mode != 'warm' && $the_mode != 'cold' ]]; then
+    iorapd_readahead_enable
+    return 0
+  fi
+
   if [[ $the_when != "aot" ]]; then
     # TODO: just in time implementation.. should probably use system server.
     return 0
@@ -250,13 +265,18 @@
   local the_when="$1" # user: aot, jit
   local the_mode="$2" # warm, cold, fadvise, mlock, etc.
   local logcat_timestamp="$3"  # timestamp from before am start.
+  local res
 
   if [[ $the_when != "aot" ]]; then
     if [[ $the_mode != 'warm' && $the_mode != 'cold' ]]; then
       # Validate that readahead completes.
       # If this fails for some reason, then this will also discard the timing of the run.
       iorapd_readahead_wait_until_finished "$package" "$activity" "$logcat_timestamp" "$timeout"
-      return $?
+      res=$?
+
+      iorapd_readahead_disable
+
+      return $res
     fi
     # Don't need to do anything for warm or cold.
     return 0
diff --git a/startop/scripts/iorap/collector b/startop/scripts/iorap/collector
index d96125f..3dc080a 100755
--- a/startop/scripts/iorap/collector
+++ b/startop/scripts/iorap/collector
@@ -322,6 +322,7 @@
   iorapd_compiler_purge_trace_file "$package" "$activity" || return $?
 
   iorapd_perfetto_enable || return $?
+  iorapd_readahead_disable || return $?
   iorapd_start || return $?
 
   # Wait for perfetto trace to finished writing itself out.
diff --git a/startop/scripts/iorap/common b/startop/scripts/iorap/common
index cb2b618..031dabf 100755
--- a/startop/scripts/iorap/common
+++ b/startop/scripts/iorap/common
@@ -45,7 +45,7 @@
   iorapd_reset # iorapd only reads this flag when initializing
 }
 
-# Enable perfetto tracing.
+# Disable perfetto tracing.
 # Subsequent launches of applications will no longer record perfetto trace protobufs.
 iorapd_perfetto_disable() {
   verbose_print 'disable perfetto'
@@ -53,6 +53,31 @@
   iorapd_reset # iorapd only reads this flag when initializing
 }
 
+# Enable readahead
+# Subsequent launches of an application will be sped up by iorapd readahead prefetching
+# (Provided an appropriate compiled trace exists for that application)
+iorapd_readahead_enable() {
+  if [[ "$(adb shell getprop iorapd.readahead.enable)" == true ]]; then
+    verbose_print 'enable readahead [already enabled]'
+    return 0
+  fi
+  verbose_print 'enable readahead [reset iorapd]'
+  adb shell setprop iorapd.readahead.enable true
+  iorapd_reset # iorapd only reads this flag when initializing
+}
+
+# Disable readahead
+# Subsequent launches of an application will be not be sped up by iorapd readahead prefetching.
+iorapd_readahead_disable() {
+  if [[ "$(adb shell getprop iorapd.readahead.enable)" == false ]]; then
+    verbose_print 'disable readahead [already disabled]'
+    return 0
+  fi
+  verbose_print 'disable readahead [reset iorapd]'
+  adb shell setprop iorapd.readahead.enable false
+  iorapd_reset # iorapd only reads this flag when initializing
+}
+
 _iorapd_path_to_data_file() {
   local package="$1"
   local activity="$2"
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
index 388c7d0..c50229a 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
@@ -16,9 +16,7 @@
 
 package com.android.framework.permission.tests;
 
-import junit.framework.TestCase;
-
-import android.media.AudioManager;
+import android.media.AudioAttributes;
 import android.os.Binder;
 import android.os.IVibratorService;
 import android.os.Process;
@@ -27,6 +25,9 @@
 import android.os.VibrationEffect;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import junit.framework.TestCase;
+
+
 /**
  * Verify that Hardware apis cannot be called without required permissions.
  */
@@ -51,7 +52,10 @@
         try {
             final VibrationEffect effect =
                     VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
-            mVibratorService.vibrate(Process.myUid(), null, effect, AudioManager.STREAM_ALARM,
+            final AudioAttributes attrs = new AudioAttributes.Builder()
+                    .setUsage(AudioAttributes.USAGE_ALARM)
+                    .build();
+            mVibratorService.vibrate(Process.myUid(), null, effect, attrs,
                     "testVibrate", new Binder());
             fail("vibrate did not throw SecurityException as expected");
         } catch (SecurityException e) {