Merge "Don't set Bluetooth volume when the Bluetooth stream changes" into pi-dev
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index 46566e7..21d6762 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -495,7 +495,15 @@
      * @hide
      */
     public boolean tasksAreFloating() {
-        return mWindowingMode == WINDOWING_MODE_FREEFORM || mWindowingMode == WINDOWING_MODE_PINNED;
+        return isFloating(mWindowingMode);
+    }
+
+    /**
+     * Returns true if the windowingMode represents a floating window.
+     * @hide
+     */
+    public static boolean isFloating(int windowingMode) {
+        return windowingMode == WINDOWING_MODE_FREEFORM || windowingMode == WINDOWING_MODE_PINNED;
     }
 
     /**
diff --git a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
index a05a8ec..83a0228 100644
--- a/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
+++ b/core/java/android/hardware/camera2/legacy/SurfaceTextureRenderer.java
@@ -529,14 +529,32 @@
     private boolean swapBuffers(EGLSurface surface)
             throws LegacyExceptionUtils.BufferQueueAbandonedException {
         boolean result = EGL14.eglSwapBuffers(mEGLDisplay, surface);
+
         int error = EGL14.eglGetError();
-        if (error == EGL14.EGL_BAD_SURFACE) {
-            throw new LegacyExceptionUtils.BufferQueueAbandonedException();
-        } else if (error != EGL14.EGL_SUCCESS) {
-            throw new IllegalStateException("swapBuffers: EGL error: 0x" +
-                    Integer.toHexString(error));
+        switch (error) {
+            case EGL14.EGL_SUCCESS:
+                return result;
+
+            // Check for an abandoned buffer queue, or other error conditions out
+            // of the user's control.
+            //
+            // From the EGL 1.4 spec (2013-12-04), Section 3.9.4 Posting Errors:
+            //
+            //   If eglSwapBuffers is called and the native window associated with
+            //   surface is no longer valid, an EGL_BAD_NATIVE_WINDOW error is
+            //   generated.
+            //
+            // We also interpret EGL_BAD_SURFACE as indicating an abandoned
+            // surface, even though the EGL spec does not document it as such, for
+            // backwards compatibility with older versions of this file.
+            case EGL14.EGL_BAD_NATIVE_WINDOW:
+            case EGL14.EGL_BAD_SURFACE:
+                throw new LegacyExceptionUtils.BufferQueueAbandonedException();
+
+            default:
+                throw new IllegalStateException(
+                        "swapBuffers: EGL error: 0x" + Integer.toHexString(error));
         }
-        return result;
     }
 
     private void checkEglError(String msg) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4d7c202..fdae191 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -86,6 +86,7 @@
 import android.view.textservice.TextServicesManager;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.ColorDisplayController;
 import com.android.internal.widget.ILockSettings;
 
 import java.io.IOException;
@@ -3147,7 +3148,9 @@
         public static final String DISPLAY_COLOR_MODE = "display_color_mode";
 
         private static final Validator DISPLAY_COLOR_MODE_VALIDATOR =
-                new SettingsValidators.InclusiveIntegerRangeValidator(0, 2);
+                new SettingsValidators.InclusiveIntegerRangeValidator(
+                        ColorDisplayController.COLOR_MODE_NATURAL,
+                        ColorDisplayController.COLOR_MODE_AUTOMATIC);
 
         /**
          * The amount of time in milliseconds before the device goes to sleep or begins
diff --git a/core/java/android/view/textclassifier/SystemTextClassifier.java b/core/java/android/view/textclassifier/SystemTextClassifier.java
index 490c389..da86b55 100644
--- a/core/java/android/view/textclassifier/SystemTextClassifier.java
+++ b/core/java/android/view/textclassifier/SystemTextClassifier.java
@@ -55,7 +55,8 @@
         mManagerService = ITextClassifierService.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.TEXT_CLASSIFICATION_SERVICE));
         mSettings = Preconditions.checkNotNull(settings);
-        mFallback = new TextClassifierImpl(context, settings);
+        mFallback = context.getSystemService(TextClassificationManager.class)
+                .getTextClassifier(TextClassifier.LOCAL);
         mPackageName = Preconditions.checkNotNull(context.getPackageName());
     }
 
diff --git a/core/java/android/view/textclassifier/TextClassificationManager.java b/core/java/android/view/textclassifier/TextClassificationManager.java
index 262d9b8..1737861 100644
--- a/core/java/android/view/textclassifier/TextClassificationManager.java
+++ b/core/java/android/view/textclassifier/TextClassificationManager.java
@@ -191,10 +191,11 @@
         synchronized (mLock) {
             if (mLocalTextClassifier == null) {
                 if (mSettings.isLocalTextClassifierEnabled()) {
-                    mLocalTextClassifier = new TextClassifierImpl(mContext, mSettings);
+                    mLocalTextClassifier =
+                            new TextClassifierImpl(mContext, mSettings, TextClassifier.NO_OP);
                 } else {
                     Log.d(LOG_TAG, "Local TextClassifier disabled");
-                    mLocalTextClassifier = TextClassifierImpl.NO_OP;
+                    mLocalTextClassifier = TextClassifier.NO_OP;
                 }
             }
             return mLocalTextClassifier;
diff --git a/core/java/android/view/textclassifier/TextClassifierImpl.java b/core/java/android/view/textclassifier/TextClassifierImpl.java
index 910fcaa..2cf265d 100644
--- a/core/java/android/view/textclassifier/TextClassifierImpl.java
+++ b/core/java/android/view/textclassifier/TextClassifierImpl.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.WorkerThread;
+import android.app.PendingIntent;
 import android.app.RemoteAction;
 import android.app.SearchManager;
 import android.content.ComponentName;
@@ -98,13 +99,18 @@
 
     private final TextClassificationConstants mSettings;
 
-    public TextClassifierImpl(Context context, TextClassificationConstants settings) {
+    public TextClassifierImpl(
+            Context context, TextClassificationConstants settings, TextClassifier fallback) {
         mContext = Preconditions.checkNotNull(context);
-        mFallback = TextClassifier.NO_OP;
+        mFallback = Preconditions.checkNotNull(fallback);
         mSettings = Preconditions.checkNotNull(settings);
         mGenerateLinksLogger = new GenerateLinksLogger(mSettings.getGenerateLinksLogSampleRate());
     }
 
+    public TextClassifierImpl(Context context, TextClassificationConstants settings) {
+        this(context, settings, TextClassifier.NO_OP);
+    }
+
     /** @inheritDoc */
     @Override
     @WorkerThread
@@ -413,6 +419,9 @@
         for (LabeledIntent labeledIntent : IntentFactory.create(
                 mContext, referenceTime, highestScoringResult, classifiedText)) {
             final RemoteAction action = labeledIntent.asRemoteAction(mContext);
+            if (action == null) {
+                continue;
+            }
             if (isPrimaryAction) {
                 // For O backwards compatibility, the first RemoteAction is also written to the
                 // legacy API fields.
@@ -601,6 +610,7 @@
             return mRequestCode;
         }
 
+        @Nullable
         RemoteAction asRemoteAction(Context context) {
             final PackageManager pm = context.getPackageManager();
             final ResolveInfo resolveInfo = pm.resolveActivity(mIntent, 0);
@@ -622,8 +632,12 @@
                 icon = Icon.createWithResource("android",
                         com.android.internal.R.drawable.ic_more_items);
             }
-            final RemoteAction action = new RemoteAction(icon, mTitle, mDescription,
-                    TextClassification.createPendingIntent(context, mIntent, mRequestCode));
+            final PendingIntent pendingIntent =
+                    TextClassification.createPendingIntent(context, mIntent, mRequestCode);
+            if (pendingIntent == null) {
+                return null;
+            }
+            final RemoteAction action = new RemoteAction(icon, mTitle, mDescription, pendingIntent);
             action.setShouldShowIcon(shouldShowIcon);
             return action;
         }
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f6ac1cc..10de449 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -123,6 +123,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 import com.android.internal.util.Preconditions;
+import com.android.internal.view.FloatingActionMode;
 import com.android.internal.widget.EditableInputConnection;
 
 import java.lang.annotation.Retention;
@@ -2215,6 +2216,15 @@
         ActionMode.Callback actionModeCallback = new TextActionModeCallback(actionMode);
         mTextActionMode = mTextView.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING);
 
+        final boolean selectableText = mTextView.isTextEditable() || mTextView.isTextSelectable();
+        if (actionMode == TextActionMode.TEXT_LINK && !selectableText
+                && mTextActionMode instanceof FloatingActionMode) {
+            // Make the toolbar outside-touchable so that it can be dismissed when the user clicks
+            // outside of it.
+            ((FloatingActionMode) mTextActionMode).setOutsideTouchable(true,
+                    () -> stopTextActionMode());
+        }
+
         final boolean selectionStarted = mTextActionMode != null;
         if (selectionStarted
                 && mTextView.isTextEditable() && !mTextView.isTextSelectable()
diff --git a/core/java/com/android/internal/app/ColorDisplayController.java b/core/java/com/android/internal/app/ColorDisplayController.java
index f1539ee..6cc964b 100644
--- a/core/java/com/android/internal/app/ColorDisplayController.java
+++ b/core/java/com/android/internal/app/ColorDisplayController.java
@@ -82,7 +82,7 @@
     public static final int AUTO_MODE_TWILIGHT = 2;
 
     @Retention(RetentionPolicy.SOURCE)
-    @IntDef({ COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED })
+    @IntDef({ COLOR_MODE_NATURAL, COLOR_MODE_BOOSTED, COLOR_MODE_SATURATED, COLOR_MODE_AUTOMATIC })
     public @interface ColorMode {}
 
     /**
@@ -103,12 +103,12 @@
      * @see #setColorMode(int)
      */
     public static final int COLOR_MODE_SATURATED = 2;
-
     /**
-     * See com.android.server.display.DisplayTransformManager.
+     * Color mode with automatic colors.
+     *
+     * @see #setColorMode(int)
      */
-    private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation";
-    private static final String PERSISTENT_PROPERTY_NATIVE_MODE = "persist.sys.sf.native_mode";
+    public static final int COLOR_MODE_AUTOMATIC = 3;
 
     private final Context mContext;
     private final int mUserId;
@@ -362,6 +362,41 @@
     }
 
     /**
+     * Get the current color mode from system properties, or return -1.
+     *
+     * See com.android.server.display.DisplayTransformManager.
+     */
+    private @ColorMode int getCurrentColorModeFromSystemProperties() {
+        int displayColorSetting = SystemProperties.getInt("persist.sys.sf.native_mode", 0);
+        if (displayColorSetting == 0) {
+            return "1.0".equals(SystemProperties.get("persist.sys.sf.color_saturation"))
+                ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
+        } else if (displayColorSetting == 1) {
+            return COLOR_MODE_SATURATED;
+        } else if (displayColorSetting == 2) {
+            return COLOR_MODE_AUTOMATIC;
+        } else {
+            return -1;
+        }
+    }
+
+    private boolean isColorModeAvailable(@ColorMode int colorMode) {
+        // SATURATED is always allowed
+        if (colorMode == COLOR_MODE_SATURATED) {
+            return true;
+        }
+
+        final int[] availableColorModes = mContext.getResources().getIntArray(
+                R.array.config_availableColorModes);
+        for (int mode : availableColorModes) {
+            if (mode == colorMode) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Get the current color mode.
      */
     public int getColorMode() {
@@ -369,17 +404,27 @@
             return COLOR_MODE_SATURATED;
         }
 
-        final int colorMode = System.getIntForUser(mContext.getContentResolver(),
+        int colorMode = System.getIntForUser(mContext.getContentResolver(),
             System.DISPLAY_COLOR_MODE, -1, mUserId);
-        if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) {
+        if (colorMode == -1) {
             // There still might be a legacy system property controlling color mode that we need to
             // respect.
-            if ("1".equals(SystemProperties.get(PERSISTENT_PROPERTY_NATIVE_MODE))) {
-                return COLOR_MODE_SATURATED;
-            }
-            return "1.0".equals(SystemProperties.get(PERSISTENT_PROPERTY_SATURATION))
-                    ? COLOR_MODE_NATURAL : COLOR_MODE_BOOSTED;
+            colorMode = getCurrentColorModeFromSystemProperties();
         }
+
+        // This happens when a color mode is no longer available (e.g., after system update or B&R)
+        // or the device does not support any color mode.
+        if (!isColorModeAvailable(colorMode)) {
+            if (colorMode == COLOR_MODE_BOOSTED && isColorModeAvailable(COLOR_MODE_NATURAL)) {
+                colorMode = COLOR_MODE_NATURAL;
+            } else if (colorMode == COLOR_MODE_SATURATED
+                && isColorModeAvailable(COLOR_MODE_AUTOMATIC)) {
+                colorMode = COLOR_MODE_AUTOMATIC;
+            } else {
+                colorMode = COLOR_MODE_SATURATED;
+            }
+        }
+
         return colorMode;
     }
 
@@ -389,7 +434,7 @@
      * @param colorMode the color mode
      */
     public void setColorMode(@ColorMode int colorMode) {
-        if (colorMode < COLOR_MODE_NATURAL || colorMode > COLOR_MODE_SATURATED) {
+        if (!isColorModeAvailable(colorMode)) {
             throw new IllegalArgumentException("Invalid colorMode: " + colorMode);
         }
         System.putIntForUser(mContext.getContentResolver(), System.DISPLAY_COLOR_MODE, colorMode,
diff --git a/core/java/com/android/internal/view/FloatingActionMode.java b/core/java/com/android/internal/view/FloatingActionMode.java
index 497e7b0..54dede6 100644
--- a/core/java/com/android/internal/view/FloatingActionMode.java
+++ b/core/java/com/android/internal/view/FloatingActionMode.java
@@ -17,6 +17,7 @@
 package com.android.internal.view;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -30,6 +31,7 @@
 import android.view.ViewParent;
 import android.view.WindowManager;
 
+import android.widget.PopupWindow;
 import com.android.internal.R;
 import com.android.internal.util.Preconditions;
 import com.android.internal.view.menu.MenuBuilder;
@@ -241,6 +243,23 @@
         }
     }
 
+    /**
+     * If this is set to true, the action mode view will dismiss itself on touch events outside of
+     * its window. This only makes sense if the action mode view is a PopupWindow that is touchable
+     * but not focusable, which means touches outside of the window will be delivered to the window
+     * behind. The default is false.
+     *
+     * This is for internal use only and the approach to this may change.
+     * @hide
+     *
+     * @param outsideTouchable whether or not this action mode is "outside touchable"
+     * @param onDismiss optional. Sets a callback for when this action mode popup dismisses itself
+     */
+    public void setOutsideTouchable(
+            boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) {
+        mFloatingToolbar.setOutsideTouchable(outsideTouchable, onDismiss);
+    }
+
     @Override
     public void onWindowFocusChanged(boolean hasWindowFocus) {
         mFloatingToolbarVisibilityHelper.setWindowFocused(hasWindowFocus);
diff --git a/core/java/com/android/internal/widget/FloatingToolbar.java b/core/java/com/android/internal/widget/FloatingToolbar.java
index f70c554..42fb1f5 100644
--- a/core/java/com/android/internal/widget/FloatingToolbar.java
+++ b/core/java/com/android/internal/widget/FloatingToolbar.java
@@ -21,6 +21,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Color;
@@ -259,6 +260,22 @@
         return mPopup.isHidden();
     }
 
+    /**
+     * If this is set to true, the action mode view will dismiss itself on touch events outside of
+     * its window. If the toolbar is already showing, it will be re-shown so that this setting takes
+     * effect immediately.
+     *
+     * @param outsideTouchable whether or not this action mode is "outside touchable"
+     * @param onDismiss optional. Sets a callback for when this action mode popup dismisses itself
+     */
+    public void setOutsideTouchable(
+            boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) {
+        if (mPopup.setOutsideTouchable(outsideTouchable, onDismiss) && isShowing()) {
+            dismiss();
+            doShow();
+        }
+    }
+
     private void doShow() {
         List<MenuItem> menuItems = getVisibleAndEnabledMenuItems(mMenu);
         menuItems.sort(mMenuItemComparator);
@@ -513,6 +530,32 @@
         }
 
         /**
+         * Makes this toolbar "outside touchable" and sets the onDismissListener.
+         * This will take effect the next time the toolbar is re-shown.
+         *
+         * @param outsideTouchable if true, the popup will be made "outside touchable" and
+         *      "non focusable". The reverse will happen if false.
+         * @param onDismiss
+         *
+         * @return true if the "outsideTouchable" setting was modified. Otherwise returns false
+         *
+         * @see PopupWindow#setOutsideTouchable(boolean)
+         * @see PopupWindow#setFocusable(boolean)
+         * @see PopupWindow.OnDismissListener
+         */
+        public boolean setOutsideTouchable(
+                boolean outsideTouchable, @Nullable PopupWindow.OnDismissListener onDismiss) {
+            boolean ret = false;
+            if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) {
+                mPopupWindow.setOutsideTouchable(outsideTouchable);
+                mPopupWindow.setFocusable(!outsideTouchable);
+                ret = true;
+            }
+            mPopupWindow.setOnDismissListener(onDismiss);
+            return ret;
+        }
+
+        /**
          * Lays out buttons for the specified menu items.
          * Requires a subsequent call to {@link #show()} to show the items.
          */
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 54864f3..5c80d4d2 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -948,6 +948,16 @@
         <!-- B y-intercept --> <item>-0.198650895</item>
     </string-array>
 
+
+    <!-- Indicate available ColorDisplayController.COLOR_MODE_xxx. -->
+    <integer-array name="config_availableColorModes">
+        <!-- Example:
+        <item>0</item>
+        <item>1</item>
+        <item>2</item>
+        -->
+    </integer-array>
+
     <!-- Indicate whether to allow the device to suspend when the screen is off
          due to the proximity sensor.  This resource should only be set to true
          if the sensor HAL correctly handles the proximity sensor as a wake-up source.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d7a1ecc..5298e5b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2964,6 +2964,7 @@
   <java-symbol type="integer" name="config_nightDisplayColorTemperatureMax" />
   <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficients" />
   <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficientsNative" />
+  <java-symbol type="array" name="config_availableColorModes" />
 
   <!-- Default first user restrictions -->
   <java-symbol type="array" name="config_defaultFirstUserRestrictions" />
diff --git a/core/tests/coretests/res/layout/activity_text_view.xml b/core/tests/coretests/res/layout/activity_text_view.xml
index d5be87d..0568683 100644
--- a/core/tests/coretests/res/layout/activity_text_view.xml
+++ b/core/tests/coretests/res/layout/activity_text_view.xml
@@ -31,6 +31,8 @@
     <TextView
         android:id="@+id/nonselectable_textview"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
+        android:layout_height="wrap_content"
+        android:focusable="false"
+        android:focusableInTouchMode="false" />
 
 </LinearLayout>
diff --git a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
index a3c24cb..c8a53cc 100644
--- a/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
+++ b/core/tests/coretests/src/android/view/textclassifier/TextClassificationManagerTest.java
@@ -18,11 +18,22 @@
 
 import static org.hamcrest.CoreMatchers.not;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
 import android.os.LocaleList;
 import android.service.textclassifier.TextClassifierService;
 import android.support.test.InstrumentationRegistry;
@@ -35,6 +46,7 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
 
 import java.util.Arrays;
 import java.util.Collections;
@@ -305,7 +317,6 @@
     public void testGetLocalTextClassifier() {
         assertTrue(mTcm.getTextClassifier(TextClassifier.LOCAL) instanceof TextClassifierImpl);
     }
-
     @Test
     public void testGetSystemTextClassifier() {
         assertTrue(
@@ -313,6 +324,48 @@
                 || mTcm.getTextClassifier(TextClassifier.SYSTEM) instanceof SystemTextClassifier);
     }
 
+    @Test
+    public void testCannotResolveIntent() {
+        final PackageManager fakePackageMgr = mock(PackageManager.class);
+
+        ResolveInfo validInfo = mContext.getPackageManager().resolveActivity(
+                new Intent(Intent.ACTION_DIAL).setData(Uri.parse("tel:+12122537077")), 0);
+        // Make packageManager fail when it gets the following intent:
+        ArgumentMatcher<Intent> toFailIntent =
+                intent -> intent.getAction().equals(Intent.ACTION_INSERT_OR_EDIT);
+
+        when(fakePackageMgr.resolveActivity(any(Intent.class), anyInt())).thenReturn(validInfo);
+        when(fakePackageMgr.resolveActivity(argThat(toFailIntent), anyInt())).thenReturn(null);
+
+        ContextWrapper fakeContext = new ContextWrapper(mContext) {
+            @Override
+            public PackageManager getPackageManager() {
+                return fakePackageMgr;
+            }
+        };
+
+        TextClassifier fallback = TextClassifier.NO_OP;
+        TextClassifier classifier = new TextClassifierImpl(
+                fakeContext, TextClassificationConstants.loadFromString(null), fallback);
+
+        String text = "Contact me at +12122537077";
+        String classifiedText = "+12122537077";
+        int startIndex = text.indexOf(classifiedText);
+        int endIndex = startIndex + classifiedText.length();
+        TextClassification.Request request = new TextClassification.Request.Builder(
+                text, startIndex, endIndex)
+                .setDefaultLocales(LOCALES)
+                .build();
+
+        TextClassification result = classifier.classifyText(request);
+        TextClassification fallbackResult = fallback.classifyText(request);
+
+        // classifier should not totally fail in which case it returns a fallback result.
+        // It should skip the failing intent and return a result for non-failing intents.
+        assertFalse(result.getActions().isEmpty());
+        assertNotSame(result, fallbackResult);
+    }
+
     private boolean isTextClassifierDisabled() {
         return mClassifier == TextClassifier.NO_OP;
     }
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 320a7ac..bcb7cf3 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -318,40 +318,52 @@
         onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position));
         sleepForFloatingToolbarPopup();
         assertFloatingToolbarIsDisplayed();
-
     }
 
     @Test
     public void testToolbarAppearsAfterLinkClickedNonselectable() throws Throwable {
-        TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
-        int position = (textLink.getStart() + textLink.getEnd()) / 2;
+        final TextView textView = mActivity.findViewById(R.id.nonselectable_textview);
+        final TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
+        final int position = (textLink.getStart() + textLink.getEnd()) / 2;
+
         onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
         sleepForFloatingToolbarPopup();
         assertFloatingToolbarIsDisplayed();
+        assertTrue(textView.hasSelection());
+
+        // toggle
+        onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
+        sleepForFloatingToolbarPopup();
+        assertFalse(textView.hasSelection());
+
+        onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
+        assertTrue(textView.hasSelection());
+
+        // click outside
+        onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(0));
+        sleepForFloatingToolbarPopup();
+        assertFalse(textView.hasSelection());
     }
 
     @Test
     public void testSelectionRemovedWhenNonselectableTextLosesFocus() throws Throwable {
-        // Add a link to both selectable and nonselectable TextViews:
-        TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview);
-        int selectablePosition = (textLink.getStart() + textLink.getEnd()) / 2;
-        textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
-        int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2;
-        TextView selectableTextView = mActivity.findViewById(R.id.textview);
-        TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview);
+        final TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
+        final int position = (textLink.getStart() + textLink.getEnd()) / 2;
+        final TextView textView = mActivity.findViewById(R.id.nonselectable_textview);
+        mActivityRule.runOnUiThread(() -> textView.setFocusableInTouchMode(true));
 
-        onView(withId(R.id.nonselectable_textview))
-                .perform(clickOnTextAtIndex(nonselectablePosition));
+        onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
         sleepForFloatingToolbarPopup();
         assertFloatingToolbarIsDisplayed();
-        assertTrue(nonselectableTextView.hasSelection());
+        assertTrue(textView.hasSelection());
 
-        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(selectablePosition));
+        mActivityRule.runOnUiThread(() -> textView.clearFocus());
+        mInstrumentation.waitForIdleSync();
         sleepForFloatingToolbarPopup();
-        assertFloatingToolbarIsDisplayed();
 
-        assertTrue(selectableTextView.hasSelection());
-        assertFalse(nonselectableTextView.hasSelection());
+        assertFalse(textView.hasSelection());
     }
 
     @Test
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 7ac1529..3a0a58e 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -86,7 +86,7 @@
  * <p>
  * Once the app has a sessionId, it can construct a MediaCrypto object from the UUID and
  * sessionId.  The MediaCrypto object is registered with the MediaCodec in the
- * {@link MediaCodec.#configure} method to enable the codec to decrypt content.
+ * {@link MediaCodec#configure} method to enable the codec to decrypt content.
  * <p>
  * When the app has constructed {@link android.media.MediaExtractor},
  * {@link android.media.MediaCodec} and {@link android.media.MediaCrypto} objects,
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index f476a6c..8a757b8 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -488,6 +488,7 @@
         private int mCompilation;
         private boolean mIsDrm;
         private boolean mNoMedia;   // flag to suppress file from appearing in media tables
+        private boolean mScanSuccess;
         private int mWidth;
         private int mHeight;
 
@@ -502,6 +503,7 @@
             mFileType = 0;
             mFileSize = fileSize;
             mIsDrm = false;
+            mScanSuccess = true;
 
             if (!isDirectory) {
                 if (!noMedia && isNoMediaFile(path)) {
@@ -623,14 +625,6 @@
                     if (noMedia) {
                         result = endFile(entry, false, false, false, false, false);
                     } else {
-                        String lowpath = path.toLowerCase(Locale.ROOT);
-                        boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0);
-                        boolean notifications = (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
-                        boolean alarms = (lowpath.indexOf(ALARMS_DIR) > 0);
-                        boolean podcasts = (lowpath.indexOf(PODCAST_DIR) > 0);
-                        boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) ||
-                            (!ringtones && !notifications && !alarms && !podcasts);
-
                         boolean isaudio = MediaFile.isAudioFileType(mFileType);
                         boolean isvideo = MediaFile.isVideoFileType(mFileType);
                         boolean isimage = MediaFile.isImageFileType(mFileType);
@@ -642,13 +636,22 @@
 
                         // we only extract metadata for audio and video files
                         if (isaudio || isvideo) {
-                            processFile(path, mimeType, this);
+                            mScanSuccess = processFile(path, mimeType, this);
                         }
 
                         if (isimage) {
-                            processImageFile(path);
+                            mScanSuccess = processImageFile(path);
                         }
 
+                        String lowpath = path.toLowerCase(Locale.ROOT);
+                        boolean ringtones = mScanSuccess && (lowpath.indexOf(RINGTONES_DIR) > 0);
+                        boolean notifications = mScanSuccess &&
+                                (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
+                        boolean alarms = mScanSuccess && (lowpath.indexOf(ALARMS_DIR) > 0);
+                        boolean podcasts = mScanSuccess && (lowpath.indexOf(PODCAST_DIR) > 0);
+                        boolean music = mScanSuccess && ((lowpath.indexOf(MUSIC_DIR) > 0) ||
+                            (!ringtones && !notifications && !alarms && !podcasts));
+
                         result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
                     }
                 }
@@ -816,16 +819,18 @@
             return genreTagValue;
         }
 
-        private void processImageFile(String path) {
+        private boolean processImageFile(String path) {
             try {
                 mBitmapOptions.outWidth = 0;
                 mBitmapOptions.outHeight = 0;
                 BitmapFactory.decodeFile(path, mBitmapOptions);
                 mWidth = mBitmapOptions.outWidth;
                 mHeight = mBitmapOptions.outHeight;
+                return mWidth > 0 && mHeight > 0;
             } catch (Throwable th) {
                 // ignore;
             }
+            return false;
         }
 
         public void setMimeType(String mimeType) {
@@ -878,7 +883,7 @@
                     }
                 } else if (MediaFile.isImageFileType(mFileType)) {
                     // FIXME - add DESCRIPTION
-                } else if (MediaFile.isAudioFileType(mFileType)) {
+                } else if (mScanSuccess && MediaFile.isAudioFileType(mFileType)) {
                     map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0) ?
                             mArtist : MediaStore.UNKNOWN_STRING);
                     map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
@@ -894,6 +899,10 @@
                     map.put(Audio.Media.DURATION, mDuration);
                     map.put(Audio.Media.COMPILATION, mCompilation);
                 }
+                if (!mScanSuccess) {
+                    // force mediaprovider to not determine the media type from the mime type
+                    map.put(Files.FileColumns.MEDIA_TYPE, 0);
+                }
             }
             return map;
         }
@@ -1001,7 +1010,7 @@
 
             Uri tableUri = mFilesUri;
             MediaInserter inserter = mMediaInserter;
-            if (!mNoMedia) {
+            if (mScanSuccess && !mNoMedia) {
                 if (MediaFile.isVideoFileType(mFileType)) {
                     tableUri = mVideoUri;
                 } else if (MediaFile.isImageFileType(mFileType)) {
@@ -1071,7 +1080,7 @@
                 values.remove(MediaStore.MediaColumns.DATA);
 
                 int mediaType = 0;
-                if (!MediaScanner.isNoMediaPath(entry.mPath)) {
+                if (mScanSuccess && !MediaScanner.isNoMediaPath(entry.mPath)) {
                     int fileType = MediaFile.getFileTypeForMimeType(mMimeType);
                     if (MediaFile.isAudioFileType(fileType)) {
                         mediaType = FileColumns.MEDIA_TYPE_AUDIO;
@@ -1890,7 +1899,7 @@
     }
 
     private native void processDirectory(String path, MediaScannerClient client);
-    private native void processFile(String path, String mimeType, MediaScannerClient client);
+    private native boolean processFile(String path, String mimeType, MediaScannerClient client);
     private native void setLocale(String locale);
 
     public native byte[] extractAlbumArt(FileDescriptor fd);
diff --git a/media/jni/android_media_MediaScanner.cpp b/media/jni/android_media_MediaScanner.cpp
index 3b475b2..c0ceb01 100644
--- a/media/jni/android_media_MediaScanner.cpp
+++ b/media/jni/android_media_MediaScanner.cpp
@@ -264,7 +264,7 @@
     env->ReleaseStringUTFChars(path, pathStr);
 }
 
-static void
+static jboolean
 android_media_MediaScanner_processFile(
         JNIEnv *env, jobject thiz, jstring path,
         jstring mimeType, jobject client)
@@ -275,17 +275,17 @@
     MediaScanner *mp = getNativeScanner_l(env, thiz);
     if (mp == NULL) {
         jniThrowException(env, kRunTimeException, "No scanner available");
-        return;
+        return false;
     }
 
     if (path == NULL) {
         jniThrowException(env, kIllegalArgumentException, NULL);
-        return;
+        return false;
     }
 
     const char *pathStr = env->GetStringUTFChars(path, NULL);
     if (pathStr == NULL) {  // Out of memory
-        return;
+        return false;
     }
 
     const char *mimeTypeStr =
@@ -293,7 +293,7 @@
     if (mimeType && mimeTypeStr == NULL) {  // Out of memory
         // ReleaseStringUTFChars can be called with an exception pending.
         env->ReleaseStringUTFChars(path, pathStr);
-        return;
+        return false;
     }
 
     MyMediaScannerClient myClient(env, client);
@@ -305,6 +305,7 @@
     if (mimeType) {
         env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
     }
+    return result != MEDIA_SCAN_RESULT_ERROR;
 }
 
 static void
@@ -421,7 +422,7 @@
 
     {
         "processFile",
-        "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)V",
+        "(Ljava/lang/String;Ljava/lang/String;Landroid/media/MediaScannerClient;)Z",
         (void *)android_media_MediaScanner_processFile
     },
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
index 5f7ba586..38c8a88 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothAdapter.java
@@ -24,6 +24,7 @@
 import android.os.ParcelUuid;
 import android.util.Log;
 
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -248,4 +249,8 @@
     public int getMaxConnectedAudioDevices() {
         return mAdapter.getMaxConnectedAudioDevices();
     }
+
+    public List<Integer> getSupportedProfiles() {
+        return mAdapter.getSupportedProfiles();
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 11854b1..0715d69 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -40,6 +40,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 
@@ -158,9 +159,13 @@
         addProfile(mPbapProfile, PbapServerProfile.NAME,
              BluetoothPbap.ACTION_CONNECTION_STATE_CHANGED);
 
-        mHearingAidProfile = new HearingAidProfile(mContext, mLocalAdapter, mDeviceManager, this);
-        addProfile(mHearingAidProfile, HearingAidProfile.NAME,
-                   BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+        List<Integer> supportedList = mLocalAdapter.getSupportedProfiles();
+        if (supportedList.contains(BluetoothProfile.HEARING_AID)) {
+            mHearingAidProfile = new HearingAidProfile(mContext, mLocalAdapter, mDeviceManager,
+                                                       this);
+            addProfile(mHearingAidProfile, HearingAidProfile.NAME,
+                       BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED);
+        }
         if (DEBUG) Log.d(TAG, "LocalBluetoothProfileManager construction complete");
     }
 
diff --git a/packages/SystemUI/res/anim/car_arrow_fade_in_rotate_down.xml b/packages/SystemUI/res/anim/car_arrow_fade_in_rotate_down.xml
new file mode 100644
index 0000000..74f38d4
--- /dev/null
+++ b/packages/SystemUI/res/anim/car_arrow_fade_in_rotate_down.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:ordering="together">
+  <objectAnimator
+      android:propertyName="rotation"
+      android:duration="0"
+      android:valueFrom="180"
+      android:valueTo="0"
+      android:interpolator="@android:interpolator/fast_out_slow_in" />
+  <objectAnimator
+      android:propertyName="alpha"
+      android:valueFrom="0.0"
+      android:valueTo="1.0"
+      android:valueType="floatType"
+      android:duration="300"
+      android:interpolator="@android:interpolator/fast_out_slow_in" />
+  <objectAnimator
+      android:propertyName="scaleX"
+      android:valueFrom="0.8"
+      android:valueTo="1.0"
+      android:valueType="floatType"
+      android:duration="300"
+      android:interpolator="@android:interpolator/fast_out_slow_in" />
+  <objectAnimator
+      android:propertyName="scaleY"
+      android:valueFrom="0.8"
+      android:valueTo="1.0"
+      android:valueType="floatType"
+      android:duration="300"
+      android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
\ No newline at end of file
diff --git a/packages/SystemUI/res/anim/car_arrow_fade_in_rotate_up.xml b/packages/SystemUI/res/anim/car_arrow_fade_in_rotate_up.xml
new file mode 100644
index 0000000..0f28297
--- /dev/null
+++ b/packages/SystemUI/res/anim/car_arrow_fade_in_rotate_up.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:ordering="together">
+  <objectAnimator
+      android:propertyName="rotation"
+      android:duration="0"
+      android:valueFrom="0"
+      android:valueTo="180"
+      android:interpolator="@android:interpolator/fast_out_slow_in" />
+  <objectAnimator
+      android:propertyName="alpha"
+      android:valueFrom="0.0"
+      android:valueTo="1.0"
+      android:valueType="floatType"
+      android:duration="300"
+      android:interpolator="@android:interpolator/fast_out_slow_in" />
+  <objectAnimator
+      android:propertyName="scaleX"
+      android:valueFrom="0.8"
+      android:valueTo="1.0"
+      android:valueType="floatType"
+      android:duration="300"
+      android:interpolator="@android:interpolator/fast_out_slow_in" />
+  <objectAnimator
+      android:propertyName="scaleY"
+      android:valueFrom="0.8"
+      android:valueTo="1.0"
+      android:valueType="floatType"
+      android:duration="300"
+      android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
\ No newline at end of file
diff --git a/packages/SystemUI/res/anim/car_arrow_fade_out.xml b/packages/SystemUI/res/anim/car_arrow_fade_out.xml
new file mode 100644
index 0000000..e6757d2
--- /dev/null
+++ b/packages/SystemUI/res/anim/car_arrow_fade_out.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+     android:ordering="together">
+  <objectAnimator
+      android:propertyName="alpha"
+      android:valueFrom="1.0"
+      android:valueTo="0.0"
+      android:valueType="floatType"
+      android:duration="300"
+      android:interpolator="@android:interpolator/fast_out_slow_in" />
+  <objectAnimator
+      android:propertyName="scaleX"
+      android:valueFrom="1.0"
+      android:valueTo="0.8"
+      android:valueType="floatType"
+      android:duration="300"
+      android:interpolator="@android:interpolator/fast_out_slow_in" />
+  <objectAnimator
+      android:propertyName="scaleY"
+      android:valueFrom="1.0"
+      android:valueTo="0.8"
+      android:valueType="floatType"
+      android:duration="300"
+      android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/car_ic_keyboard_arrow_down.xml b/packages/SystemUI/res/drawable/car_ic_keyboard_arrow_down.xml
new file mode 100644
index 0000000..3709aa5
--- /dev/null
+++ b/packages/SystemUI/res/drawable/car_ic_keyboard_arrow_down.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~
+  ~ Copyright (C) 2018 Google Inc.
+  ~
+  ~ 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.
+  ~
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="44dp"
+    android:height="44dp"
+    android:viewportWidth="48.0"
+    android:viewportHeight="48.0">
+  <path
+      android:pathData="M14.83 16.42L24 25.59l9.17-9.17L36 19.25l-12 12-12-12z"
+      android:fillColor="#ffffff"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/car_ic_music.xml b/packages/SystemUI/res/drawable/car_ic_music.xml
new file mode 100644
index 0000000..f90cd69
--- /dev/null
+++ b/packages/SystemUI/res/drawable/car_ic_music.xml
@@ -0,0 +1,30 @@
+<!--
+    Copyright (C) 2016 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="56dp"
+    android:height="56dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+
+  <path
+      android:fillAlpha=".1"
+      android:strokeAlpha=".1"
+      android:pathData="M0 0h48v48H0z" />
+  <path
+      android:fillColor="@color/car_grey_50"
+      android:pathData="M24 2C14.06 2 6 10.06 6 20v14c0 3.31 2.69 6 6 6h6V24h-8v-4c0-7.73 6.27-14
+14-14s14 6.27 14 14v4h-8v16h6c3.31 0 6-2.69 6-6V20c0-9.94-8.06-18-18-18z" />
+</vector>
diff --git a/packages/SystemUI/res/drawable/car_ic_navigation.xml b/packages/SystemUI/res/drawable/car_ic_navigation.xml
new file mode 100644
index 0000000..328efa0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/car_ic_navigation.xml
@@ -0,0 +1,28 @@
+<?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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="37dp"
+    android:viewportWidth="32.0"
+    android:viewportHeight="37.0">
+  <path
+      android:pathData="M16.62,0.61L31.33,35.21C31.55,35.72 31.31,36.3 30.8,36.52C30.48,36.66 30.12,36.62 29.83,36.42L15.7,26.44L1.58,36.42C1.13,36.73 0.5,36.63 0.18,36.18C-0.02,35.89 -0.06,35.53 0.08,35.21L14.78,0.61C15,0.1 15.59,-0.14 16.1,0.08C16.33,0.18 16.52,0.37 16.62,0.61Z"
+      android:strokeColor="#00000000"
+      android:fillType="evenOdd"
+      android:fillColor="@color/car_grey_50"
+      android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/car_ic_notification_2.xml b/packages/SystemUI/res/drawable/car_ic_notification_2.xml
new file mode 100644
index 0000000..c74ae15
--- /dev/null
+++ b/packages/SystemUI/res/drawable/car_ic_notification_2.xml
@@ -0,0 +1,31 @@
+<?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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="38dp"
+    android:viewportWidth="32"
+    android:viewportHeight="38" >
+  <group
+      android:translateX="-6"
+      android:translateY="-3">
+    <path
+        android:pathData="M26.6195649,6.98115478 C31.5083629,8.85235985 34.9817444,13.6069337 34.9817444,19.1767606 L34.9817444,27.9542254 L38,27.9542254 L38,34.2161972 L6,34.2161972 L6,27.9542254 L9.01825558,27.9542254 L9.01825558,19.1767606 C9.01825558,13.6069337 12.4916371,8.85235985 17.3804351,6.98115478 C17.723241,4.726863 19.6609451,3 22,3 C24.3390549,3 26.276759,4.726863 26.6195649,6.98115478 Z M17.326572,36.3035211 L26.673428,36.3035211 C26.673428,38.8973148 24.581063,41 22,41 C19.418937,41 17.326572,38.8973148 17.326572,36.3035211 Z"
+        android:strokeColor="#00000000"
+        android:fillType="evenOdd"
+        android:fillColor="@color/car_grey_50" />
+  </group>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_lock_lockdown.xml b/packages/SystemUI/res/drawable/ic_lock_lockdown.xml
index 65b9813..d591aa8 100644
--- a/packages/SystemUI/res/drawable/ic_lock_lockdown.xml
+++ b/packages/SystemUI/res/drawable/ic_lock_lockdown.xml
@@ -17,10 +17,10 @@
         android:width="24.0dp"
         android:height="24.0dp"
         android:viewportWidth="24.0"
-        android:viewportHeight="24.0">
+        android:viewportHeight="24.0"
+        android:tint="?attr/colorControlNormal">
 
     <path
         android:fillColor="#000000"
-        android:alpha="0.87"
         android:pathData="M18.0,8.0l-1.0,0.0L17.0,6.0c0.0,-2.8 -2.2,-5.0 -5.0,-5.0C9.2,1.0 7.0,3.2 7.0,6.0l0.0,2.0L6.0,8.0c-1.1,0.0 -2.0,0.9 -2.0,2.0l0.0,10.0c0.0,1.1 0.9,2.0 2.0,2.0l12.0,0.0c1.1,0.0 2.0,-0.9 2.0,-2.0L20.0,10.0C20.0,8.9 19.1,8.0 18.0,8.0zM12.0,17.0c-1.1,0.0 -2.0,-0.9 -2.0,-2.0s0.9,-2.0 2.0,-2.0c1.1,0.0 2.0,0.9 2.0,2.0S13.1,17.0 12.0,17.0zM15.1,8.0L8.9,8.0L8.9,6.0c0.0,-1.7 1.4,-3.1 3.1,-3.1c1.7,0.0 3.1,1.4 3.1,3.1L15.1,8.0z"/>
 </vector>
diff --git a/packages/SystemUI/res/layout/car_volume_dialog.xml b/packages/SystemUI/res/layout/car_volume_dialog.xml
index 36bc85d..a6beaa1 100644
--- a/packages/SystemUI/res/layout/car_volume_dialog.xml
+++ b/packages/SystemUI/res/layout/car_volume_dialog.xml
@@ -13,26 +13,19 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<FrameLayout
+<androidx.car.widget.PagedListView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
-    android:id="@+id/volume_dialog"
-    android:clipChildren="false"
+    android:background="@drawable/car_card_rounded_background"
+    android:id="@+id/volume_list"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:layout_marginStart="@dimen/car_margin"
     android:layout_marginEnd="@dimen/car_margin"
-    android:theme="@style/qs_theme" >
-    <androidx.car.widget.PagedListView
-        android:id="@+id/volume_list"
-        android:background="@drawable/car_rounded_bg_bottom"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:minWidth="@dimen/volume_dialog_panel_width"
-        android:theme="?attr/dialogListTheme"
-        app:dividerStartMargin="@dimen/car_keyline_1"
-        app:dividerEndMargin="@dimen/car_keyline_1"
-        app:gutter="none"
-        app:showPagedListViewDivider="true"
-        app:scrollBarEnabled="false" />
-</FrameLayout>
+    android:minWidth="@dimen/volume_dialog_panel_width"
+    android:theme="@style/Theme.Car.NoActionBar"
+    app:dividerStartMargin="@dimen/car_keyline_1"
+    app:dividerEndMargin="@dimen/car_keyline_1"
+    app:gutter="none"
+    app:showPagedListViewDivider="true"
+    app:scrollBarEnabled="false" />
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/MetricsLoggerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/MetricsLoggerCompat.java
new file mode 100644
index 0000000..e93e78d
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/MetricsLoggerCompat.java
@@ -0,0 +1,40 @@
+/*
+ * 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 com.android.systemui.shared.system;
+
+import com.android.internal.logging.MetricsLogger;
+
+public class MetricsLoggerCompat {
+
+    private final MetricsLogger mMetricsLogger;
+
+    public MetricsLoggerCompat() {
+        mMetricsLogger = new MetricsLogger();
+    }
+
+    public void action(int category) {
+        mMetricsLogger.action(category);
+    }
+
+    public void action(int category, int value) {
+        mMetricsLogger.action(category, value);
+    }
+
+    public void visible(int category) {
+        mMetricsLogger.visible(category);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
index a0fdb9d..b0bda16 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenState.java
@@ -36,7 +36,13 @@
      * Delay entering low power mode when animating to make sure that we'll have
      * time to move all elements into their final positions while still at 60 fps.
      */
-    private static final int ENTER_DOZE_DELAY = 3000;
+    private static final int ENTER_DOZE_DELAY = 6000;
+    /**
+     * Hide wallpaper earlier when entering low power mode. The gap between
+     * hiding the wallpaper and changing the display mode is necessary to hide
+     * the black frame that's inherent to hardware specs.
+     */
+    public static final int ENTER_DOZE_HIDE_WALLPAPER_DELAY = 2000;
 
     private final DozeMachine.Service mDozeService;
     private final Handler mHandler;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index d2d9c4c..85fac16 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -30,6 +30,7 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.doze.AlwaysOnDisplayPolicy;
+import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.tuner.TunerService;
 
 import java.io.PrintWriter;
@@ -153,7 +154,8 @@
      * @return duration in millis.
      */
     public long getWallpaperAodDuration() {
-        return mAlwaysOnPolicy.wallpaperVisibilityDuration;
+        return shouldControlScreenOff() ? DozeScreenState.ENTER_DOZE_HIDE_WALLPAPER_DELAY
+                : mAlwaysOnPolicy.wallpaperVisibilityDuration;
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 64abfe2..f1a7183 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.volume;
 
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorSet;
 import android.annotation.Nullable;
 import android.app.Dialog;
 import android.app.KeyguardManager;
@@ -24,6 +27,7 @@
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.os.Debug;
@@ -80,7 +84,6 @@
 
     private Window mWindow;
     private CustomDialog mDialog;
-    private ViewGroup mDialogView;
     private PagedListView mListView;
     private ListItemAdapter mPagedListAdapter;
     private final List<ListItem> mVolumeLineItems = new ArrayList<>();
@@ -99,29 +102,6 @@
     private boolean mHovering = false;
     private boolean mExpanded;
 
-    private final View.OnClickListener mSupplementalIconListener = v -> {
-        mExpanded = !mExpanded;
-        if (mExpanded) {
-            for (VolumeRow row : mRows) {
-                // Adding the items which are not coming from default stream.
-                if (!row.defaultStream) {
-                    addSeekbarListItem(row, null);
-                }
-            }
-        } else {
-            // Only keeping the default stream if it is not expended.
-            Iterator itr = mVolumeLineItems.iterator();
-            while (itr.hasNext()) {
-                SeekbarListItem item = (SeekbarListItem) itr.next();
-                VolumeRow row = findRow(item);
-                if (!row.defaultStream) {
-                    itr.remove();
-                }
-            }
-        }
-        mPagedListAdapter.notifyDataSetChanged();
-    };
-
     public CarVolumeDialogImpl(Context context) {
         mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
         mController = Dependency.get(VolumeDialogController.class);
@@ -175,31 +155,31 @@
         mDialog.setCanceledOnTouchOutside(true);
         mDialog.setContentView(R.layout.car_volume_dialog);
         mDialog.setOnShowListener(dialog -> {
-            mDialogView.setTranslationY(-mDialogView.getHeight());
-            mDialogView.setAlpha(0);
-            mDialogView.animate()
+            mListView.setTranslationY(-mListView.getHeight());
+            mListView.setAlpha(0);
+            mListView.animate()
                 .alpha(1)
                 .translationY(0)
                 .setDuration(300)
                 .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
                 .start();
         });
-        mDialogView = (ViewGroup) mDialog.findViewById(R.id.volume_dialog);
-        mDialogView.setOnHoverListener((v, event) -> {
+        mListView = (PagedListView) mWindow.findViewById(R.id.volume_list);
+        mListView.setOnHoverListener((v, event) -> {
             int action = event.getActionMasked();
             mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
                 || (action == MotionEvent.ACTION_HOVER_MOVE);
             rescheduleTimeoutH();
             return true;
         });
-        mListView = (PagedListView) mWindow.findViewById(R.id.volume_list);
 
-        // TODO: apply tint to the supplement icon.
-        addSeekbarListItem(addVolumeRow(AudioManager.STREAM_MUSIC, R.drawable.ic_volume_media,
-            R.drawable.car_ic_arrow_drop_up, true, true), mSupplementalIconListener);
-        addVolumeRow(AudioManager.STREAM_RING, R.drawable.ic_volume_ringer, 0,
+        addSeekbarListItem(addVolumeRow(AudioManager.STREAM_MUSIC, R.drawable.car_ic_music,
+            R.drawable.car_ic_keyboard_arrow_down, true, true),
+            new ExpandIconListener());
+        // We map AudioManager.STREAM_RING to a navigation icon for demo.
+        addVolumeRow(AudioManager.STREAM_RING, R.drawable.car_ic_navigation, 0,
             true, false);
-        addVolumeRow(AudioManager.STREAM_ALARM, R.drawable.ic_volume_alarm, 0,
+        addVolumeRow(AudioManager.STREAM_NOTIFICATION, R.drawable.car_ic_notification_2, 0,
             true, false);
 
         mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems),
@@ -248,9 +228,13 @@
         SeekbarListItem listItem =
             new SeekbarListItem(mContext, volumeMax, currentVolume,
                 new VolumeSeekBarChangeListener(volumeRow), null);
-        listItem.setPrimaryActionIcon(volumeRow.primaryActionIcon);
+        Drawable primaryIcon = mContext.getResources().getDrawable(volumeRow.primaryActionIcon);
+        listItem.setPrimaryActionIcon(primaryIcon);
         if (volumeRow.supplementalIcon != 0) {
-            listItem.setSupplementalIcon(volumeRow.supplementalIcon, true, supplementalIconOnClickListener);
+            Drawable supplementalIcon = mContext.getResources()
+                .getDrawable(volumeRow.supplementalIcon);
+            listItem.setSupplementalIcon(supplementalIcon, true,
+                supplementalIconOnClickListener);
         } else {
             listItem.setSupplementalEmptyIcon(true);
         }
@@ -309,14 +293,14 @@
         mHandler.removeMessages(H.DISMISS);
         mHandler.removeMessages(H.SHOW);
         if (!mShowing) return;
-        mDialogView.animate().cancel();
+        mListView.animate().cancel();
         mShowing = false;
 
-        mDialogView.setTranslationY(0);
-        mDialogView.setAlpha(1);
-        mDialogView.animate()
+        mListView.setTranslationY(0);
+        mListView.setAlpha(1);
+        mListView.animate()
             .alpha(0)
-            .translationY(-mDialogView.getHeight())
+            .translationY(-mListView.getHeight())
             .setDuration(250)
             .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
             .withEndAction(() -> mHandler.postDelayed(() -> {
@@ -487,7 +471,7 @@
 
         @Override
         public void onLayoutDirectionChanged(int layoutDirection) {
-            mDialogView.setLayoutDirection(layoutDirection);
+            mListView.setLayoutDirection(layoutDirection);
         }
 
         @Override
@@ -642,6 +626,45 @@
         }
     }
 
+    private final class ExpandIconListener implements View.OnClickListener {
+        @Override
+        public void onClick(final View v) {
+            mExpanded = !mExpanded;
+            Animator inAnimator;
+            if (mExpanded) {
+                for (VolumeRow row : mRows) {
+                    // Adding the items which are not coming from default stream.
+                    if (!row.defaultStream) {
+                        addSeekbarListItem(row, null);
+                    }
+                }
+                inAnimator = AnimatorInflater.loadAnimator(
+                    mContext, R.anim.car_arrow_fade_in_rotate_up);
+            } else {
+                // Only keeping the default stream if it is not expended.
+                Iterator itr = mVolumeLineItems.iterator();
+                while (itr.hasNext()) {
+                    SeekbarListItem item = (SeekbarListItem) itr.next();
+                    VolumeRow row = findRow(item);
+                    if (!row.defaultStream) {
+                        itr.remove();
+                    }
+                }
+                inAnimator = AnimatorInflater.loadAnimator(
+                    mContext, R.anim.car_arrow_fade_in_rotate_down);
+            }
+
+            Animator outAnimator = AnimatorInflater.loadAnimator(
+                mContext, R.anim.car_arrow_fade_out);
+            inAnimator.setStartDelay(100);
+            AnimatorSet animators = new AnimatorSet();
+            animators.playTogether(outAnimator, inAnimator);
+            animators.setTarget(v);
+            animators.start();
+            mPagedListAdapter.notifyDataSetChanged();
+        }
+    }
+
     private static class VolumeRow {
         private int stream;
         private StreamState ss;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
index 550a35d..532019f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeParametersTest.java
@@ -22,6 +22,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.doze.DozeScreenState;
 import com.android.systemui.statusbar.phone.DozeParameters.IntInOutMatcher;
 
 import org.junit.Assert;
@@ -218,6 +219,15 @@
         verify(dozeParameters.getPowerManager()).setDozeAfterScreenOff(eq(false));
     }
 
+    @Test
+    public void test_getWallpaperAodDuration_when_shouldControlScreenOff() {
+        TestableDozeParameters dozeParameters = new TestableDozeParameters(getContext());
+        dozeParameters.setControlScreenOffAnimation(true);
+        Assert.assertEquals("wallpaper hides faster when controlling screen off",
+                dozeParameters.getWallpaperAodDuration(),
+                DozeScreenState.ENTER_DOZE_HIDE_WALLPAPER_DELAY);
+    }
+
     private class TestableDozeParameters extends DozeParameters {
         private PowerManager mPowerManager;
 
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index aaa42e5..a4d0dc8 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -2411,19 +2411,26 @@
         // after moving out of the inactive state, so no need to worry about that.
         boolean becomeInactive = false;
         if (mState != STATE_ACTIVE) {
-            scheduleReportActiveLocked(type, Process.myUid());
+            // Motion shouldn't affect light state, if it's already in doze-light or maintenance
+            boolean lightIdle = mLightState == LIGHT_STATE_IDLE
+                    || mLightState == LIGHT_STATE_WAITING_FOR_NETWORK
+                    || mLightState == LIGHT_STATE_IDLE_MAINTENANCE;
+            if (!lightIdle) {
+                // Only switch to active state if we're not in either idle state
+                scheduleReportActiveLocked(type, Process.myUid());
+                addEvent(EVENT_NORMAL, type);
+            }
             mState = STATE_ACTIVE;
             mInactiveTimeout = timeout;
             mCurIdleBudget = 0;
             mMaintenanceStartTime = 0;
             EventLogTags.writeDeviceIdle(mState, type);
-            addEvent(EVENT_NORMAL, type);
             becomeInactive = true;
         }
         if (mLightState == LIGHT_STATE_OVERRIDE) {
             // We went out of light idle mode because we had started deep idle mode...  let's
             // now go back and reset things so we resume light idling if appropriate.
-            mLightState = STATE_ACTIVE;
+            mLightState = LIGHT_STATE_ACTIVE;
             EventLogTags.writeDeviceIdleLight(mLightState, type);
             becomeInactive = true;
         }
@@ -2811,6 +2818,8 @@
         pw.println("    If no DURATION is specified, 10 seconds is used");
         pw.println("    If [-r] option is used, then the package is removed from temp whitelist "
                 + "and any [-d] is ignored");
+        pw.println("  motion");
+        pw.println("    Simulate a motion event to bring the device out of deep doze");
     }
 
     class Shell extends ShellCommand {
@@ -3207,13 +3216,28 @@
                 }
             } else {
                 synchronized (this) {
-                    for (int j=0; j<mPowerSaveWhitelistApps.size(); j++) {
+                    for (int j = 0; j < mPowerSaveWhitelistApps.size(); j++) {
                         pw.print(mPowerSaveWhitelistApps.keyAt(j));
                         pw.print(",");
                         pw.println(mPowerSaveWhitelistApps.valueAt(j));
                     }
                 }
             }
+        } else if ("motion".equals(cmd)) {
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            synchronized (this) {
+                long token = Binder.clearCallingIdentity();
+                try {
+                    motionLocked();
+                    pw.print("Light state: ");
+                    pw.print(lightStateToString(mLightState));
+                    pw.print(", deep state: ");
+                    pw.println(stateToString(mState));
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            }
         } else {
             return shell.handleDefaultCommands(cmd);
         }
diff --git a/services/core/java/com/android/server/am/ActivityDisplay.java b/services/core/java/com/android/server/am/ActivityDisplay.java
index fac3f92..0f42103 100644
--- a/services/core/java/com/android/server/am/ActivityDisplay.java
+++ b/services/core/java/com/android/server/am/ActivityDisplay.java
@@ -420,7 +420,9 @@
                 if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
                     continue;
                 }
-                otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+                otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN, false /* animate */,
+                        false /* showRecents */, false /* enteringSplitScreenMode */,
+                        true /* deferEnsuringVisibility */);
             }
         } finally {
             final ActivityStack topFullscreenStack =
@@ -450,7 +452,7 @@
                 }
                 otherStack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
                         false /* animate */, false /* showRecents */,
-                        true /* enteringSplitScreenMode */);
+                        true /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */);
             }
         } finally {
             mSupervisor.mWindowManager.continueSurfaceLayout();
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ac06ddd..193d3f4 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11271,7 +11271,7 @@
                     stack.moveToFront("setTaskWindowingModeSplitScreenPrimary", task);
                 }
                 stack.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, animate, showRecents,
-                        false /* enteringSplitScreenMode */);
+                        false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */);
                 return windowingMode != task.getWindowingMode();
             } finally {
                 Binder.restoreCallingIdentity(ident);
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 9fb3e11..e86850e 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -515,11 +515,11 @@
     @Override
     public void setWindowingMode(int windowingMode) {
         setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
-                false /* enteringSplitScreenMode */);
+                false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */);
     }
 
     void setWindowingMode(int preferredWindowingMode, boolean animate, boolean showRecents,
-            boolean enteringSplitScreenMode) {
+            boolean enteringSplitScreenMode, boolean deferEnsuringVisibility) {
         final boolean creating = mWindowContainerController == null;
         final int currentMode = getWindowingMode();
         final ActivityDisplay display = getDisplay();
@@ -555,7 +555,9 @@
                 // doesn't support split-screen mode, go ahead an dismiss split-screen and display a
                 // warning toast about it.
                 mService.mTaskChangeNotificationController.notifyActivityDismissingDockedStack();
-                display.getSplitScreenPrimaryStack().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+                display.getSplitScreenPrimaryStack().setWindowingMode(WINDOWING_MODE_FULLSCREEN,
+                        false /* animate */, false /* showRecents */,
+                        false /* enteringSplitScreenMode */, true /* deferEnsuringVisibility */);
             }
         }
 
@@ -646,9 +648,7 @@
             wm.continueSurfaceLayout();
         }
 
-        // Don't ensure visible activities if the windowing mode change was a side effect of us
-        // entering split-screen mode.
-        if (!enteringSplitScreenMode) {
+        if (!deferEnsuringVisibility) {
             mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, PRESERVE_WINDOWS);
             mStackSupervisor.resumeFocusedStackTopActivityLocked();
         }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 1cd1dd6..9a5b1a6 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -1820,7 +1820,7 @@
             final StackWindowController stackController = mStack.getWindowContainerController();
             stackController.adjustConfigurationForBounds(bounds, insetBounds,
                     mTmpNonDecorBounds, mTmpStableBounds, overrideWidth, overrideHeight, density,
-                    config, parentConfig);
+                    config, parentConfig, getWindowingMode());
         } else {
             throw new IllegalArgumentException("Expected stack when calculating override config");
         }
diff --git a/services/core/java/com/android/server/display/ColorDisplayService.java b/services/core/java/com/android/server/display/ColorDisplayService.java
index 37035c3..030c915 100644
--- a/services/core/java/com/android/server/display/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/ColorDisplayService.java
@@ -293,7 +293,7 @@
             mColorMatrixAnimator.cancel();
         }
 
-        setCoefficientMatrix(getContext(), mode == ColorDisplayController.COLOR_MODE_SATURATED);
+        setCoefficientMatrix(getContext(), DisplayTransformManager.isColorModeNative(mode));
         setMatrix(mController.getColorTemperature(), mMatrixNight);
 
         final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
diff --git a/services/core/java/com/android/server/display/DisplayTransformManager.java b/services/core/java/com/android/server/display/DisplayTransformManager.java
index a94f049..57d88e1 100644
--- a/services/core/java/com/android/server/display/DisplayTransformManager.java
+++ b/services/core/java/com/android/server/display/DisplayTransformManager.java
@@ -60,14 +60,24 @@
     private static final int SURFACE_FLINGER_TRANSACTION_DALTONIZER = 1014;
 
     private static final String PERSISTENT_PROPERTY_SATURATION = "persist.sys.sf.color_saturation";
-    private static final String PERSISTENT_PROPERTY_NATIVE_MODE = "persist.sys.sf.native_mode";
+    private static final String PERSISTENT_PROPERTY_DISPLAY_COLOR = "persist.sys.sf.native_mode";
 
+    /**
+     * SurfaceFlinger global saturation factor.
+     */
     private static final int SURFACE_FLINGER_TRANSACTION_SATURATION = 1022;
-    private static final int SURFACE_FLINGER_TRANSACTION_NATIVE_MODE = 1023;
+    /**
+     * SurfaceFlinger display color (managed, unmanaged, etc.).
+     */
+    private static final int SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR = 1023;
 
     private static final float COLOR_SATURATION_NATURAL = 1.0f;
     private static final float COLOR_SATURATION_BOOSTED = 1.1f;
 
+    private static final int DISPLAY_COLOR_MANAGED = 0;
+    private static final int DISPLAY_COLOR_UNMANAGED = 1;
+    private static final int DISPLAY_COLOR_ENHANCED = 2;
+
     /**
      * Map of level -> color transformation matrix.
      */
@@ -220,20 +230,37 @@
         }
     }
 
+    /**
+     * Return true when colors are stretched from the working color space to the
+     * native color space.
+     */
     public static boolean isNativeModeEnabled() {
-        return SystemProperties.getBoolean(PERSISTENT_PROPERTY_NATIVE_MODE, false);
+        return SystemProperties.getInt(PERSISTENT_PROPERTY_DISPLAY_COLOR,
+                DISPLAY_COLOR_MANAGED) != DISPLAY_COLOR_MANAGED;
+    }
+
+    /**
+     * Return true when the specified colorMode stretches colors from the
+     * working color space to the native color space.
+     */
+    public static boolean isColorModeNative(int colorMode) {
+        return !(colorMode == ColorDisplayController.COLOR_MODE_NATURAL ||
+                 colorMode == ColorDisplayController.COLOR_MODE_BOOSTED);
     }
 
     public boolean setColorMode(int colorMode, float[] nightDisplayMatrix) {
         if (colorMode == ColorDisplayController.COLOR_MODE_NATURAL) {
             applySaturation(COLOR_SATURATION_NATURAL);
-            setNativeMode(false);
+            setDisplayColor(DISPLAY_COLOR_MANAGED);
         } else if (colorMode == ColorDisplayController.COLOR_MODE_BOOSTED) {
             applySaturation(COLOR_SATURATION_BOOSTED);
-            setNativeMode(false);
+            setDisplayColor(DISPLAY_COLOR_MANAGED);
         } else if (colorMode == ColorDisplayController.COLOR_MODE_SATURATED) {
             applySaturation(COLOR_SATURATION_NATURAL);
-            setNativeMode(true);
+            setDisplayColor(DISPLAY_COLOR_UNMANAGED);
+        } else if (colorMode == ColorDisplayController.COLOR_MODE_AUTOMATIC) {
+            applySaturation(COLOR_SATURATION_NATURAL);
+            setDisplayColor(DISPLAY_COLOR_ENHANCED);
         }
         setColorMatrix(LEVEL_COLOR_MATRIX_NIGHT_DISPLAY, nightDisplayMatrix);
 
@@ -265,17 +292,17 @@
     /**
      * Toggles native mode on/off in SurfaceFlinger.
      */
-    private void setNativeMode(boolean enabled) {
-        SystemProperties.set(PERSISTENT_PROPERTY_NATIVE_MODE, enabled ? "1" : "0");
+    private void setDisplayColor(int color) {
+        SystemProperties.set(PERSISTENT_PROPERTY_DISPLAY_COLOR, Integer.toString(color));
         final IBinder flinger = ServiceManager.getService(SURFACE_FLINGER);
         if (flinger != null) {
             final Parcel data = Parcel.obtain();
             data.writeInterfaceToken("android.ui.ISurfaceComposer");
-            data.writeInt(enabled ? 1 : 0);
+            data.writeInt(color);
             try {
-                flinger.transact(SURFACE_FLINGER_TRANSACTION_NATIVE_MODE, data, null, 0);
+                flinger.transact(SURFACE_FLINGER_TRANSACTION_DISPLAY_COLOR, data, null, 0);
             } catch (RemoteException ex) {
-                Log.e(TAG, "Failed to set native mode", ex);
+                Log.e(TAG, "Failed to set display color", ex);
             } finally {
                 data.recycle();
             }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index bd4210c..1d5b1a3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -6094,7 +6094,7 @@
         // Cancel any pending remote recents animations before handling the button itself. In the
         // case where we are going home and the recents animation has already started, just cancel
         // the recents animation, leaving the home stack in place for the pending start activity
-        if (isNavBarVirtKey && !down) {
+        if (isNavBarVirtKey && !down && !canceled) {
             boolean isHomeKey = keyCode == KeyEvent.KEYCODE_HOME;
             mActivityManagerInternal.cancelRecentsAnimation(!isHomeKey);
         }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 4fd31ff..38fb30e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3906,7 +3906,7 @@
      * with {@link #WindowState#assignLayer}
      */
     void assignRelativeLayerForImeTargetChild(SurfaceControl.Transaction t, WindowContainer child) {
-        t.setRelativeLayer(child.getSurfaceControl(), mImeWindowsContainers.getSurfaceControl(), 1);
+        child.assignRelativeLayer(t, mImeWindowsContainers.getSurfaceControl(), 1);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index ddb67b4..653850a 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.app.WindowConfiguration;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -244,12 +245,15 @@
     }
 
     /**
-     * Adjusts the screen size in dp's for the {@param config} for the given params.
+     * Adjusts the screen size in dp's for the {@param config} for the given params. The provided
+     * params represent the desired state of a configuration change. Since this utility is used
+     * before mContainer has been updated, any relevant properties (like {@param windowingMode})
+     * need to be passed in.
      */
     public void adjustConfigurationForBounds(Rect bounds, Rect insetBounds,
             Rect nonDecorBounds, Rect stableBounds, boolean overrideWidth,
             boolean overrideHeight, float density, Configuration config,
-            Configuration parentConfig) {
+            Configuration parentConfig, int windowingMode) {
         synchronized (mWindowMap) {
             final TaskStack stack = mContainer;
             final DisplayContent displayContent = stack.getDisplayContent();
@@ -272,10 +276,10 @@
             config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null);
             boolean intersectParentBounds = false;
 
-            if (stack.getWindowConfiguration().tasksAreFloating()) {
+            if (WindowConfiguration.isFloating(windowingMode)) {
                 // Floating tasks should not be resized to the screen's bounds.
 
-                if (stack.inPinnedWindowingMode()
+                if (windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED
                         && bounds.width() == mTmpDisplayBounds.width()
                         && bounds.height() == mTmpDisplayBounds.height()) {
                     // If the bounds we are animating is the same as the fullscreen stack
@@ -316,7 +320,7 @@
             config.screenWidthDp = width;
             config.screenHeightDp = height;
             config.smallestScreenWidthDp = getSmallestWidthForTaskBounds(
-                    insetBounds != null ? insetBounds : bounds, density);
+                    insetBounds != null ? insetBounds : bounds, density, windowingMode);
         }
     }
 
@@ -338,11 +342,12 @@
     }
 
     /**
-     * Calculates the smallest width for a task given the {@param bounds}.
+     * Calculates the smallest width for a task given the target {@param bounds} and
+     * {@param windowingMode}. Avoid using values from mContainer since they can be out-of-date.
      *
      * @return the smallest width to be used in the Configuration, in dips
      */
-    private int getSmallestWidthForTaskBounds(Rect bounds, float density) {
+    private int getSmallestWidthForTaskBounds(Rect bounds, float density, int windowingMode) {
         final DisplayContent displayContent = mContainer.getDisplayContent();
         final DisplayInfo displayInfo = displayContent.getDisplayInfo();
 
@@ -350,7 +355,7 @@
                 bounds.height() == displayInfo.logicalHeight)) {
             // If the bounds are fullscreen, return the value of the fullscreen configuration
             return displayContent.getConfiguration().smallestScreenWidthDp;
-        } else if (mContainer.getWindowConfiguration().tasksAreFloating()) {
+        } else if (WindowConfiguration.isFloating(windowingMode)) {
             // For floating tasks, calculate the smallest width from the bounds of the task
             return (int) (Math.min(bounds.width(), bounds.height()) / density);
         } else {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 04554ef..10dfdf2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7489,7 +7489,7 @@
 
     boolean hasWideColorGamutSupport() {
         return mHasWideColorGamutSupport &&
-                !SystemProperties.getBoolean("persist.sys.sf.native_mode", false);
+                SystemProperties.getInt("persist.sys.sf.native_mode", 0) != 1;
     }
 
     void updateNonSystemOverlayWindowsVisibilityIfNeeded(WindowState win, boolean surfaceShown) {
diff --git a/services/net/java/android/net/apf/ApfCapabilities.java b/services/net/java/android/net/apf/ApfCapabilities.java
index 703b415..dec8ca2 100644
--- a/services/net/java/android/net/apf/ApfCapabilities.java
+++ b/services/net/java/android/net/apf/ApfCapabilities.java
@@ -49,4 +49,14 @@
         return String.format("%s{version: %d, maxSize: %d, format: %d}", getClass().getSimpleName(),
                 apfVersionSupported, maximumApfProgramSize, apfPacketFormat);
     }
+
+    /**
+     * Returns true if the APF interpreter advertises support for the data buffer access opcodes
+     * LDDW and STDW.
+     *
+     * Full LDDW and STDW support is present from APFv4 on.
+     */
+    public boolean hasDataAccess() {
+        return apfVersionSupported >= 4;
+    }
 }
diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java
index 9a97ba7..2bf6e92 100644
--- a/services/net/java/android/net/apf/ApfFilter.java
+++ b/services/net/java/android/net/apf/ApfFilter.java
@@ -24,6 +24,7 @@
 import static com.android.internal.util.BitUtils.getUint8;
 import static com.android.internal.util.BitUtils.uint32;
 
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -102,6 +103,70 @@
         UPDATE_EXPIRY   // APF program updated for expiry
     }
 
+    /**
+     * APF packet counters.
+     *
+     * Packet counters are 32bit big-endian values, and allocated near the end of the APF data
+     * buffer, using negative byte offsets, where -4 is equivalent to maximumApfProgramSize - 4,
+     * the last writable 32bit word.
+     */
+    @VisibleForTesting
+    private static enum Counter {
+        RESERVED_OOB,  // Points to offset 0 from the end of the buffer (out-of-bounds)
+        TOTAL_PACKETS,
+        PASSED_ARP,
+        PASSED_DHCP,
+        PASSED_IPV4,
+        PASSED_IPV6_NON_ICMP,
+        PASSED_IPV4_UNICAST,
+        PASSED_IPV6_ICMP,
+        PASSED_IPV6_UNICAST_NON_ICMP,
+        PASSED_ARP_NON_IPV4,
+        PASSED_ARP_UNKNOWN,
+        PASSED_ARP_UNICAST_REPLY,
+        PASSED_NON_IP_UNICAST,
+        DROPPED_ETH_BROADCAST,
+        DROPPED_RA,
+        DROPPED_GARP_REPLY,
+        DROPPED_ARP_OTHER_HOST,
+        DROPPED_IPV4_L2_BROADCAST,
+        DROPPED_IPV4_BROADCAST_ADDR,
+        DROPPED_IPV4_BROADCAST_NET,
+        DROPPED_IPV4_MULTICAST,
+        DROPPED_IPV6_ROUTER_SOLICITATION,
+        DROPPED_IPV6_MULTICAST_NA,
+        DROPPED_IPV6_MULTICAST,
+        DROPPED_IPV6_MULTICAST_PING,
+        DROPPED_IPV6_NON_ICMP_MULTICAST,
+        DROPPED_802_3_FRAME,
+        DROPPED_ETHERTYPE_BLACKLISTED;
+
+        // Returns the negative byte offset from the end of the APF data segment for
+        // a given counter.
+        public int offset() {
+            return - this.ordinal() * 4;  // Currently, all counters are 32bit long.
+        }
+
+        // Returns the total size of the data segment in bytes.
+        public static int totalSize() {
+            return (Counter.class.getEnumConstants().length - 1) * 4;
+        }
+    }
+
+    /**
+     * When APFv4 is supported, loads R1 with the offset of the specified counter.
+     */
+    private void maybeSetCounter(ApfGenerator gen, Counter c) {
+        if (mApfCapabilities.hasDataAccess()) {
+            gen.addLoadImmediate(Register.R1, c.offset());
+        }
+    }
+
+    // When APFv4 is supported, these point to the trampolines generated by emitEpilogue().
+    // Otherwise, they're just aliases for PASS_LABEL and DROP_LABEL.
+    private final String mCountAndPassLabel;
+    private final String mCountAndDropLabel;
+
     // Thread to listen for RAs.
     @VisibleForTesting
     class ReceiveThread extends Thread {
@@ -289,6 +354,16 @@
         mDrop802_3Frames = config.ieee802_3Filter;
         mContext = context;
 
+        if (mApfCapabilities.hasDataAccess()) {
+            mCountAndPassLabel = "countAndPass";
+            mCountAndDropLabel = "countAndDrop";
+        } else {
+            // APFv4 unsupported: turn jumps to the counter trampolines to immediately PASS or DROP,
+            // preserving the original pre-APFv4 behavior.
+            mCountAndPassLabel = ApfGenerator.PASS_LABEL;
+            mCountAndDropLabel = ApfGenerator.DROP_LABEL;
+        }
+
         // Now fill the black list from the passed array
         mEthTypeBlackList = filterEthTypeBlackList(config.ethTypeBlackList);
 
@@ -302,6 +377,10 @@
                 new IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED));
     }
 
+    public synchronized void setDataSnapshot(byte[] data) {
+        mDataSnapshot = data;
+    }
+
     private void log(String s) {
         Log.d(TAG, "(" + mInterfaceParams.name + "): " + s);
     }
@@ -350,6 +429,10 @@
         try {
             mHardwareAddress = mInterfaceParams.macAddr.toByteArray();
             synchronized(this) {
+                // Clear APF memory.
+                byte[] zeroes = new byte[mApfCapabilities.maximumApfProgramSize];
+                mIpClientCallback.installPacketFilter(zeroes);
+
                 // Install basic filters
                 installNewProgramLocked();
             }
@@ -729,7 +812,8 @@
                     gen.addJumpIfR0LessThan(filterLifetime, nextFilterLabel);
                 }
             }
-            gen.addJump(gen.DROP_LABEL);
+            maybeSetCounter(gen, Counter.DROPPED_RA);
+            gen.addJump(mCountAndDropLabel);
             gen.defineLabel(nextFilterLabel);
             return filterLifetime;
         }
@@ -764,6 +848,16 @@
     @GuardedBy("this")
     private byte[] mLastInstalledProgram;
 
+    /**
+     * For debugging only. Contains the latest APF buffer snapshot captured from the firmware.
+     *
+     * A typical size for this buffer is 4KB. It is present only if the WiFi HAL supports
+     * IWifiStaIface#readApfPacketFilterData(), and the APF interpreter advertised support for
+     * the opcodes to access the data buffer (LDDW and STDW).
+     */
+    @GuardedBy("this") @Nullable
+    private byte[] mDataSnapshot;
+
     // How many times the program was updated since we started.
     @GuardedBy("this")
     private int mNumProgramUpdates = 0;
@@ -799,31 +893,37 @@
 
         // Pass if not ARP IPv4.
         gen.addLoadImmediate(Register.R0, ARP_HEADER_OFFSET);
-        gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, gen.PASS_LABEL);
+        maybeSetCounter(gen, Counter.PASSED_ARP_NON_IPV4);
+        gen.addJumpIfBytesNotEqual(Register.R0, ARP_IPV4_HEADER, mCountAndPassLabel);
 
         // Pass if unknown ARP opcode.
         gen.addLoad16(Register.R0, ARP_OPCODE_OFFSET);
         gen.addJumpIfR0Equals(ARP_OPCODE_REQUEST, checkTargetIPv4); // Skip to unicast check
-        gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, gen.PASS_LABEL);
+        maybeSetCounter(gen, Counter.PASSED_ARP_UNKNOWN);
+        gen.addJumpIfR0NotEquals(ARP_OPCODE_REPLY, mCountAndPassLabel);
 
         // Pass if unicast reply.
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, gen.PASS_LABEL);
+        maybeSetCounter(gen, Counter.PASSED_ARP_UNICAST_REPLY);
+        gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
 
         // Either a unicast request, a unicast reply, or a broadcast reply.
         gen.defineLabel(checkTargetIPv4);
         if (mIPv4Address == null) {
             // When there is no IPv4 address, drop GARP replies (b/29404209).
             gen.addLoad32(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
-            gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, gen.DROP_LABEL);
+            maybeSetCounter(gen, Counter.DROPPED_GARP_REPLY);
+            gen.addJumpIfR0Equals(IPV4_ANY_HOST_ADDRESS, mCountAndDropLabel);
         } else {
             // When there is an IPv4 address, drop unicast/broadcast requests
             // and broadcast replies with a different target IPv4 address.
             gen.addLoadImmediate(Register.R0, ARP_TARGET_IP_ADDRESS_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, gen.DROP_LABEL);
+            maybeSetCounter(gen, Counter.DROPPED_ARP_OTHER_HOST);
+            gen.addJumpIfBytesNotEqual(Register.R0, mIPv4Address, mCountAndDropLabel);
         }
 
-        gen.addJump(gen.PASS_LABEL);
+        maybeSetCounter(gen, Counter.PASSED_ARP);
+        gen.addJump(mCountAndPassLabel);
     }
 
     /**
@@ -866,7 +966,8 @@
             // NOTE: Relies on R1 containing IPv4 header offset.
             gen.addAddR1();
             gen.addJumpIfBytesNotEqual(Register.R0, mHardwareAddress, skipDhcpv4Filter);
-            gen.addJump(gen.PASS_LABEL);
+            maybeSetCounter(gen, Counter.PASSED_DHCP);
+            gen.addJump(mCountAndPassLabel);
 
             // Drop all multicasts/broadcasts.
             gen.defineLabel(skipDhcpv4Filter);
@@ -874,24 +975,31 @@
             // If IPv4 destination address is in multicast range, drop.
             gen.addLoad8(Register.R0, IPV4_DEST_ADDR_OFFSET);
             gen.addAnd(0xf0);
-            gen.addJumpIfR0Equals(0xe0, gen.DROP_LABEL);
+            maybeSetCounter(gen, Counter.DROPPED_IPV4_MULTICAST);
+            gen.addJumpIfR0Equals(0xe0, mCountAndDropLabel);
 
             // If IPv4 broadcast packet, drop regardless of L2 (b/30231088).
+            maybeSetCounter(gen, Counter.DROPPED_IPV4_BROADCAST_ADDR);
             gen.addLoad32(Register.R0, IPV4_DEST_ADDR_OFFSET);
-            gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, gen.DROP_LABEL);
+            gen.addJumpIfR0Equals(IPV4_BROADCAST_ADDRESS, mCountAndDropLabel);
             if (mIPv4Address != null && mIPv4PrefixLength < 31) {
+                maybeSetCounter(gen, Counter.DROPPED_IPV4_BROADCAST_NET);
                 int broadcastAddr = ipv4BroadcastAddress(mIPv4Address, mIPv4PrefixLength);
-                gen.addJumpIfR0Equals(broadcastAddr, gen.DROP_LABEL);
+                gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel);
             }
 
             // If L2 broadcast packet, drop.
+            // TODO: can we invert this condition to fall through to the common pass case below?
+            maybeSetCounter(gen, Counter.PASSED_IPV4_UNICAST);
             gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-            gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, gen.PASS_LABEL);
-            gen.addJump(gen.DROP_LABEL);
+            gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
+            maybeSetCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
+            gen.addJump(mCountAndDropLabel);
         }
 
         // Otherwise, pass
-        gen.addJump(gen.PASS_LABEL);
+        maybeSetCounter(gen, Counter.PASSED_IPV4);
+        gen.addJump(mCountAndPassLabel);
     }
 
 
@@ -938,14 +1046,17 @@
 
             // Drop all other packets sent to ff00::/8 (multicast prefix).
             gen.defineLabel(dropAllIPv6MulticastsLabel);
+            maybeSetCounter(gen, Counter.DROPPED_IPV6_NON_ICMP_MULTICAST);
             gen.addLoad8(Register.R0, IPV6_DEST_ADDR_OFFSET);
-            gen.addJumpIfR0Equals(0xff, gen.DROP_LABEL);
+            gen.addJumpIfR0Equals(0xff, mCountAndDropLabel);
             // Not multicast. Pass.
-            gen.addJump(gen.PASS_LABEL);
+            maybeSetCounter(gen, Counter.PASSED_IPV6_UNICAST_NON_ICMP);
+            gen.addJump(mCountAndPassLabel);
             gen.defineLabel(skipIPv6MulticastFilterLabel);
         } else {
             // If not ICMPv6, pass.
-            gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, gen.PASS_LABEL);
+            maybeSetCounter(gen, Counter.PASSED_IPV6_NON_ICMP);
+            gen.addJumpIfR0NotEquals(IPPROTO_ICMPV6, mCountAndPassLabel);
         }
 
         // If we got this far, the packet is ICMPv6.  Drop some specific types.
@@ -954,7 +1065,8 @@
         String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA";
         gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET);
         // Drop all router solicitations (b/32833400)
-        gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, gen.DROP_LABEL);
+        maybeSetCounter(gen, Counter.DROPPED_IPV6_ROUTER_SOLICITATION);
+        gen.addJumpIfR0Equals(ICMPV6_ROUTER_SOLICITATION, mCountAndDropLabel);
         // If not neighbor announcements, skip filter.
         gen.addJumpIfR0NotEquals(ICMPV6_NEIGHBOR_ADVERTISEMENT, skipUnsolicitedMulticastNALabel);
         // If to ff02::1, drop.
@@ -962,7 +1074,8 @@
         gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET);
         gen.addJumpIfBytesNotEqual(Register.R0, IPV6_ALL_NODES_ADDRESS,
                 skipUnsolicitedMulticastNALabel);
-        gen.addJump(gen.DROP_LABEL);
+        maybeSetCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
+        gen.addJump(mCountAndDropLabel);
         gen.defineLabel(skipUnsolicitedMulticastNALabel);
     }
 
@@ -985,10 +1098,18 @@
      * </ul>
      */
     @GuardedBy("this")
-    private ApfGenerator beginProgramLocked() throws IllegalInstructionException {
+    private ApfGenerator emitPrologueLocked() throws IllegalInstructionException {
         // This is guaranteed to succeed because of the check in maybeCreate.
         ApfGenerator gen = new ApfGenerator(mApfCapabilities.apfVersionSupported);
 
+        if (mApfCapabilities.hasDataAccess()) {
+            // Increment TOTAL_PACKETS
+            maybeSetCounter(gen, Counter.TOTAL_PACKETS);
+            gen.addLoadData(Register.R0, 0);  // load counter
+            gen.addAdd(1);
+            gen.addStoreData(Register.R0, 0);  // write-back counter
+        }
+
         // Here's a basic summary of what the initial program does:
         //
         // if it's a 802.3 Frame (ethtype < 0x0600):
@@ -1009,12 +1130,14 @@
 
         if (mDrop802_3Frames) {
             // drop 802.3 frames (ethtype < 0x0600)
-            gen.addJumpIfR0LessThan(ETH_TYPE_MIN, gen.DROP_LABEL);
+            maybeSetCounter(gen, Counter.DROPPED_802_3_FRAME);
+            gen.addJumpIfR0LessThan(ETH_TYPE_MIN, mCountAndDropLabel);
         }
 
         // Handle ether-type black list
+        maybeSetCounter(gen, Counter.DROPPED_ETHERTYPE_BLACKLISTED);
         for (int p : mEthTypeBlackList) {
-            gen.addJumpIfR0Equals(p, gen.DROP_LABEL);
+            gen.addJumpIfR0Equals(p, mCountAndDropLabel);
         }
 
         // Add ARP filters:
@@ -1041,8 +1164,10 @@
 
         // Drop non-IP non-ARP broadcasts, pass the rest
         gen.addLoadImmediate(Register.R0, ETH_DEST_ADDR_OFFSET);
-        gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, gen.PASS_LABEL);
-        gen.addJump(gen.DROP_LABEL);
+        maybeSetCounter(gen, Counter.PASSED_NON_IP_UNICAST);
+        gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
+        maybeSetCounter(gen, Counter.DROPPED_ETH_BROADCAST);
+        gen.addJump(mCountAndDropLabel);
 
         // Add IPv6 filters:
         gen.defineLabel(ipv6FilterLabel);
@@ -1051,6 +1176,39 @@
     }
 
     /**
+     * Append packet counting epilogue to the APF program.
+     *
+     * Currently, the epilogue consists of two trampolines which count passed and dropped packets
+     * before jumping to the actual PASS and DROP labels.
+     */
+    @GuardedBy("this")
+    private void emitEpilogue(ApfGenerator gen) throws IllegalInstructionException {
+        // If APFv4 is unsupported, no epilogue is necessary: if execution reached this far, it
+        // will just fall-through to the PASS label.
+        if (!mApfCapabilities.hasDataAccess()) return;
+
+        // Execution will reach the bottom of the program if none of the filters match,
+        // which will pass the packet to the application processor.
+        maybeSetCounter(gen, Counter.PASSED_IPV6_ICMP);
+
+        // Append the count & pass trampoline, which increments the counter at the data address
+        // pointed to by R1, then jumps to the pass label. This saves a few bytes over inserting
+        // the entire sequence inline for every counter.
+        gen.defineLabel(mCountAndPassLabel);
+        gen.addLoadData(Register.R0, 0);   // R0 = *(R1 + 0)
+        gen.addAdd(1);                     // R0++
+        gen.addStoreData(Register.R0, 0);  // *(R1 + 0) = R0
+        gen.addJump(gen.PASS_LABEL);
+
+        // Same as above for the count & drop trampoline.
+        gen.defineLabel(mCountAndDropLabel);
+        gen.addLoadData(Register.R0, 0);   // R0 = *(R1 + 0)
+        gen.addAdd(1);                     // R0++
+        gen.addStoreData(Register.R0, 0);  // *(R1 + 0) = R0
+        gen.addJump(gen.DROP_LABEL);
+    }
+
+    /**
      * Generate and install a new filter program.
      */
     @GuardedBy("this")
@@ -1060,22 +1218,39 @@
         ArrayList<Ra> rasToFilter = new ArrayList<>();
         final byte[] program;
         long programMinLifetime = Long.MAX_VALUE;
+        long maximumApfProgramSize = mApfCapabilities.maximumApfProgramSize;
+        if (mApfCapabilities.hasDataAccess()) {
+            // Reserve space for the counters.
+            maximumApfProgramSize -= Counter.totalSize();
+        }
+
         try {
             // Step 1: Determine how many RA filters we can fit in the program.
-            ApfGenerator gen = beginProgramLocked();
+            ApfGenerator gen = emitPrologueLocked();
+
+            // The epilogue normally goes after the RA filters, but add it early to include its
+            // length when estimating the total.
+            emitEpilogue(gen);
+
+            // Can't fit the program even without any RA filters?
+            if (gen.programLengthOverEstimate() > maximumApfProgramSize) {
+                Log.e(TAG, "Program exceeds maximum size " + maximumApfProgramSize);
+                return;
+            }
+
             for (Ra ra : mRas) {
                 ra.generateFilterLocked(gen);
                 // Stop if we get too big.
-                if (gen.programLengthOverEstimate() > mApfCapabilities.maximumApfProgramSize) break;
+                if (gen.programLengthOverEstimate() > maximumApfProgramSize) break;
                 rasToFilter.add(ra);
             }
+
             // Step 2: Actually generate the program
-            gen = beginProgramLocked();
+            gen = emitPrologueLocked();
             for (Ra ra : rasToFilter) {
                 programMinLifetime = Math.min(programMinLifetime, ra.generateFilterLocked(gen));
             }
-            // Execution will reach the end of the program if no filters match, which will pass the
-            // packet to the AP.
+            emitEpilogue(gen);
             program = gen.generate();
         } catch (IllegalInstructionException|IllegalStateException e) {
             Log.e(TAG, "Failed to generate APF program.", e);
@@ -1278,6 +1453,23 @@
         installNewProgramLocked();
     }
 
+    static public long counterValue(byte[] data, Counter counter)
+            throws ArrayIndexOutOfBoundsException {
+        // Follow the same wrap-around addressing scheme of the interpreter.
+        int offset = counter.offset();
+        if (offset < 0) {
+            offset = data.length + offset;
+        }
+
+        // Decode 32bit big-endian integer into a long so we can count up beyond 2^31.
+        long value = 0;
+        for (int i = 0; i < 4; i++) {
+            value = value << 8 | (data[offset] & 0xFF);
+            offset++;
+        }
+        return value;
+    }
+
     public synchronized void dump(IndentingPrintWriter pw) {
         pw.println("Capabilities: " + mApfCapabilities);
         pw.println("Receive thread: " + (mReceiveThread != null ? "RUNNING" : "STOPPED"));
@@ -1319,6 +1511,32 @@
             pw.println(HexDump.toHexString(mLastInstalledProgram, false /* lowercase */));
             pw.decreaseIndent();
         }
+
+        pw.println("APF packet counters: ");
+        pw.increaseIndent();
+        if (!mApfCapabilities.hasDataAccess()) {
+            pw.println("APF counters not supported");
+        } else if (mDataSnapshot == null) {
+            pw.println("No last snapshot.");
+        } else {
+            try {
+                Counter[] counters = Counter.class.getEnumConstants();
+                for (Counter c : Arrays.asList(counters).subList(1, counters.length)) {
+                    long value = counterValue(mDataSnapshot, c);
+                    // Only print non-zero counters
+                    if (value != 0) {
+                        pw.println(c.toString() + ": " + value);
+                    }
+                }
+            } catch (ArrayIndexOutOfBoundsException e) {
+                pw.println("Uh-oh: " + e);
+            }
+            if (VDBG) {
+                pw.println("Raw data dump: ");
+                pw.println(HexDump.dumpHexString(mDataSnapshot));
+            }
+        }
+        pw.decreaseIndent();
     }
 
     // TODO: move to android.net.NetworkUtils
diff --git a/services/net/java/android/net/apf/ApfGenerator.java b/services/net/java/android/net/apf/ApfGenerator.java
index 99b2fc6..87a1b5e 100644
--- a/services/net/java/android/net/apf/ApfGenerator.java
+++ b/services/net/java/android/net/apf/ApfGenerator.java
@@ -378,8 +378,7 @@
     }
 
     /**
-     * Returns true if the specified {@code version} is supported by the ApfGenerator, otherwise
-     * false.
+     * Returns true if the ApfGenerator supports the specified {@code version}, otherwise false.
      */
     public static boolean supportsVersion(int version) {
         return version >= MIN_APF_VERSION;
@@ -753,7 +752,7 @@
 
     /**
      * Add an instruction to the end of the program to jump to {@code target} if the bytes of the
-     * packet at, an offset specified by {@code register}, match {@code bytes}.
+     * packet at an offset specified by {@code register} match {@code bytes}.
      */
     public ApfGenerator addJumpIfBytesNotEqual(Register register, byte[] bytes, String target)
             throws IllegalInstructionException {
diff --git a/services/net/java/android/net/ip/IpClient.java b/services/net/java/android/net/ip/IpClient.java
index 9fdb31e..d6999dd 100644
--- a/services/net/java/android/net/ip/IpClient.java
+++ b/services/net/java/android/net/ip/IpClient.java
@@ -624,7 +624,14 @@
     private ApfFilter mApfFilter;
     private boolean mMulticastFiltering;
     private long mStartTimeMillis;
-    private byte[] mApfDataSnapshot;
+
+    /**
+     * Reading the snapshot is an asynchronous operation initiated by invoking
+     * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an
+     * EVENT_READ_PACKET_FILTER_COMPLETE message. The mApfDataSnapshotComplete condition variable
+     * signals when a new snapshot is ready.
+     */
+    private final ConditionVariable mApfDataSnapshotComplete = new ConditionVariable();
 
     public static class Dependencies {
         public INetworkManagementService getNMS() {
@@ -881,13 +888,21 @@
         final ProvisioningConfiguration provisioningConfig = mConfiguration;
         final ApfCapabilities apfCapabilities = (provisioningConfig != null)
                 ? provisioningConfig.mApfCapabilities : null;
-        final byte[] apfDataSnapshot = mApfDataSnapshot;
 
         IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
         pw.println(mTag + " APF dump:");
         pw.increaseIndent();
         if (apfFilter != null) {
+            if (apfCapabilities.hasDataAccess()) {
+                // Request a new snapshot, then wait for it.
+                mApfDataSnapshotComplete.close();
+                mCallback.startReadPacketFilter();
+                if (!mApfDataSnapshotComplete.block(1000)) {
+                    pw.print("TIMEOUT: DUMPING STALE APF SNAPSHOT");
+                }
+            }
             apfFilter.dump(pw);
+
         } else {
             pw.print("No active ApfFilter; ");
             if (provisioningConfig == null) {
@@ -899,15 +914,6 @@
             }
         }
         pw.decreaseIndent();
-        pw.println(mTag + " latest APF data snapshot: ");
-        pw.increaseIndent();
-        if (apfDataSnapshot != null) {
-            pw.println(HexDump.dumpHexString(apfDataSnapshot));
-        } else {
-            pw.println("No last snapshot.");
-        }
-        pw.decreaseIndent();
-
         pw.println();
         pw.println(mTag + " current ProvisioningConfiguration:");
         pw.increaseIndent();
@@ -1704,7 +1710,10 @@
                 }
 
                 case EVENT_READ_PACKET_FILTER_COMPLETE: {
-                    mApfDataSnapshot = (byte[]) msg.obj;
+                    if (mApfFilter != null) {
+                        mApfFilter.setDataSnapshot((byte[]) msg.obj);
+                    }
+                    mApfDataSnapshotComplete.open();
                     break;
                 }
 
diff --git a/services/tests/servicestests/src/com/android/server/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/ColorDisplayServiceTest.java
index 43701e9..c004074 100644
--- a/services/tests/servicestests/src/com/android/server/ColorDisplayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/ColorDisplayServiceTest.java
@@ -898,6 +898,10 @@
 
     @Test
     public void accessibility_colorInversion_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
         setAccessibilityColorInversion(true);
         setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
 
@@ -909,6 +913,10 @@
 
     @Test
     public void accessibility_colorCorrection_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
         setAccessibilityColorCorrection(true);
         setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
 
@@ -920,6 +928,10 @@
 
     @Test
     public void accessibility_all_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
         setAccessibilityColorCorrection(true);
         setAccessibilityColorInversion(true);
         setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
@@ -932,6 +944,10 @@
 
     @Test
     public void accessibility_none_transformActivated() {
+        if (!mContext.getResources().getConfiguration().isScreenWideColorGamut()) {
+            return;
+        }
+
         setAccessibilityColorCorrection(false);
         setAccessibilityColorInversion(false);
         setColorMode(ColorDisplayController.COLOR_MODE_NATURAL);
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
index 41e446b..513c1ec 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowConfigurationTests.java
@@ -208,7 +208,8 @@
         final WindowConfiguration winConfig = config.windowConfiguration;
         stackController.adjustConfigurationForBounds(bounds, null /*insetBounds*/,
                 new Rect() /*nonDecorBounds*/, new Rect() /*stableBounds*/, false /*overrideWidth*/,
-                false /*overrideHeight*/, mDisplayInfo.logicalDensityDpi, config, parentConfig);
+                false /*overrideHeight*/, mDisplayInfo.logicalDensityDpi, config, parentConfig,
+                windowingMode);
         // Assert that both expected and actual are null or are equal to each other
 
         assertEquals(expectedConfigBounds, winConfig.getAppBounds());
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
index d74defc..2e4740b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowTestUtils.java
@@ -34,6 +34,7 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyFloat;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -72,7 +73,7 @@
             config.windowConfiguration.setBounds(bounds);
             return null;
         }).when(controller).adjustConfigurationForBounds(any(), any(), any(), any(),
-                anyBoolean(), anyBoolean(), anyFloat(), any(), any());
+                anyBoolean(), anyBoolean(), anyFloat(), any(), any(), anyInt());
 
         return controller;
     }