Merge "Add explicit method to dismiss Keyguard"
diff --git a/Android.mk b/Android.mk
index 78c9c1d..7103f67 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1130,7 +1130,9 @@
 		-proofread $(OUT_DOCS)/$(LOCAL_MODULE)-proofread.txt \
 		-sdkvalues $(OUT_DOCS) \
 		-hdf android.whichdoc offline \
-		-referenceonly
+		-referenceonly \
+		-resourcesdir $(LOCAL_PATH)/docs/html/reference/images/ \
+		-resourcesoutdir reference/android/images/
 
 LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR:=build/tools/droiddoc/templates-sdk
 
diff --git a/apct-tests/perftests/core/Android.mk b/apct-tests/perftests/core/Android.mk
index 5fe2f02..3a7f945 100644
--- a/apct-tests/perftests/core/Android.mk
+++ b/apct-tests/perftests/core/Android.mk
@@ -12,8 +12,11 @@
 
 LOCAL_PACKAGE_NAME := CorePerfTests
 
+LOCAL_JNI_SHARED_LIBRARIES := libperftestscore_jni
+
 # Use google-fonts/dancing-script for the performance metrics
 LOCAL_ASSET_DIR := $(TOP)/external/google-fonts/dancing-script
 
 include $(BUILD_PACKAGE)
 
+include $(call all-makefiles-under, $(LOCAL_PATH))
diff --git a/apct-tests/perftests/core/jni/Android.mk b/apct-tests/perftests/core/jni/Android.mk
new file mode 100644
index 0000000..d4c3f1e
--- /dev/null
+++ b/apct-tests/perftests/core/jni/Android.mk
@@ -0,0 +1,17 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_SDK_VERSION := 21
+
+LOCAL_SRC_FILES:= \
+    SystemPerfTest.cpp \
+
+LOCAL_C_INCLUDES += \
+    $(JNI_H_INCLUDE)
+
+LOCAL_MODULE := libperftestscore_jni
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/apct-tests/perftests/core/jni/SystemPerfTest.cpp b/apct-tests/perftests/core/jni/SystemPerfTest.cpp
new file mode 100644
index 0000000..eb55408
--- /dev/null
+++ b/apct-tests/perftests/core/jni/SystemPerfTest.cpp
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+
+#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
+
+static void jintarrayArgumentNoop(JNIEnv*, jclass, jintArray, jint) {
+}
+
+static jint jintarrayGetLength(JNIEnv* env, jclass, jintArray jarray) {
+    const jsize len = env->GetArrayLength(jarray);
+    return static_cast<jint>(len);
+}
+
+static jint jintarrayCriticalAccess(JNIEnv* env, jclass, jintArray jarray, jint index) {
+    const jsize len = env->GetArrayLength(jarray);
+    if (index < 0 || index >= len) {
+        return -1;
+    }
+    jint* data = (jint*) env->GetPrimitiveArrayCritical(jarray, 0);
+    jint ret = data[index];
+    env->ReleasePrimitiveArrayCritical(jarray, data, 0);
+    return ret;
+}
+
+static jint jintarrayBasicAccess(JNIEnv* env, jclass, jintArray jarray, jint index) {
+    const jsize len = env->GetArrayLength(jarray);
+    if (index < 0 || index >= len) {
+        return -1;
+    }
+    jint* data = env->GetIntArrayElements(jarray, 0);
+    jint ret = data[index];
+    env->ReleaseIntArrayElements(jarray, data, 0);
+    return ret;
+}
+
+static const JNINativeMethod sMethods[] = {
+    {"jintarrayArgumentNoop", "([II)V", (void *) jintarrayArgumentNoop},
+    {"jintarrayGetLength", "([I)I", (void *) jintarrayGetLength},
+    {"jintarrayCriticalAccess", "([II)I", (void *) jintarrayCriticalAccess},
+    {"jintarrayBasicAccess", "([II)I", (void *) jintarrayBasicAccess},
+};
+
+static int registerNativeMethods(JNIEnv* env, const char* className,
+        const JNINativeMethod* gMethods, int numMethods) {
+    jclass clazz = env->FindClass(className);
+    if (clazz == NULL) {
+        return JNI_FALSE;
+    }
+    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
+        return JNI_FALSE;
+    }
+    return JNI_TRUE;
+}
+
+jint JNI_OnLoad(JavaVM* jvm, void*) {
+    JNIEnv *env = NULL;
+    if (jvm->GetEnv((void**) &env, JNI_VERSION_1_6)) {
+        return JNI_ERR;
+    }
+
+    if (registerNativeMethods(env, "java/lang/perftests/SystemPerfTest",
+            sMethods, NELEM(sMethods)) == -1) {
+        return JNI_ERR;
+    }
+
+    return JNI_VERSION_1_6;
+}
diff --git a/apct-tests/perftests/core/src/java/lang/perftests/SystemPerfTest.java b/apct-tests/perftests/core/src/java/lang/perftests/SystemPerfTest.java
index 1e9c49c..afc9d0c 100644
--- a/apct-tests/perftests/core/src/java/lang/perftests/SystemPerfTest.java
+++ b/apct-tests/perftests/core/src/java/lang/perftests/SystemPerfTest.java
@@ -20,6 +20,9 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
+
+import dalvik.annotation.optimization.FastNative;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -29,6 +32,11 @@
 @RunWith(AndroidJUnit4.class)
 @LargeTest
 public class SystemPerfTest {
+
+    static {
+        System.loadLibrary("perftestscore_jni");
+    }
+
     @Rule
     public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
@@ -60,4 +68,49 @@
             state.resumeTiming();
         }
     }
+
+    @Test
+    public void testJniArrayNoop() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final int[] data = new int[450];
+        while (state.keepRunning()) {
+            jintarrayArgumentNoop(data, data.length);
+        }
+    }
+
+    @Test
+    public void testJniArrayGetLength() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final int[] data = new int[450];
+        while (state.keepRunning()) {
+            jintarrayGetLength(data);
+        }
+    }
+
+    @Test
+    public void testJniArrayCriticalAccess() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final int[] data = new int[450];
+        while (state.keepRunning()) {
+            jintarrayCriticalAccess(data, 50);
+        }
+    }
+
+    @Test
+    public void testJniArrayBasicAccess() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final int[] data = new int[450];
+        while (state.keepRunning()) {
+            jintarrayBasicAccess(data, 50);
+        }
+    }
+
+    @FastNative
+    private static native void jintarrayArgumentNoop(int[] array, int length);
+    @FastNative
+    private static native int jintarrayGetLength(int[] array);
+    @FastNative
+    private static native int jintarrayCriticalAccess(int[] array, int index);
+    @FastNative
+    private static native int jintarrayBasicAccess(int[] array, int index);
 }
diff --git a/api/current.txt b/api/current.txt
index 40b57a3..2e543e4 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -289,6 +289,10 @@
     field public static final int autoLink = 16842928; // 0x10100b0
     field public static final int autoMirrored = 16843754; // 0x10103ea
     field public static final int autoRemoveFromRecents = 16843847; // 0x1010447
+    field public static final int autoSizeMinTextSize = 16844088; // 0x1010538
+    field public static final int autoSizeStepGranularity = 16844086; // 0x1010536
+    field public static final int autoSizeStepSizeSet = 16844087; // 0x1010537
+    field public static final int autoSizeText = 16844085; // 0x1010535
     field public static final int autoStart = 16843445; // 0x10102b5
     field public static final deprecated int autoText = 16843114; // 0x101016a
     field public static final int autoUrlDetect = 16843404; // 0x101028c
@@ -1357,6 +1361,7 @@
     field public static final int toYDelta = 16843209; // 0x10101c9
     field public static final int toYScale = 16843205; // 0x10101c5
     field public static final int toolbarStyle = 16843946; // 0x10104aa
+    field public static final int tooltip = 16844084; // 0x1010534
     field public static final int top = 16843182; // 0x10101ae
     field public static final int topBright = 16842955; // 0x10100cb
     field public static final int topDark = 16842951; // 0x10100c7
@@ -3496,6 +3501,7 @@
     method public int getRequestedOrientation();
     method public final android.view.SearchEvent getSearchEvent();
     method public int getTaskId();
+    method public android.text.TextAssistant getTextAssistant();
     method public final java.lang.CharSequence getTitle();
     method public final int getTitleColor();
     method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3645,6 +3651,7 @@
     method public final void setResult(int, android.content.Intent);
     method public final deprecated void setSecondaryProgress(int);
     method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
+    method public void setTextAssistant(android.text.TextAssistant);
     method public void setTitle(java.lang.CharSequence);
     method public void setTitle(int);
     method public deprecated void setTitleColor(int);
@@ -5971,6 +5978,8 @@
     method public void onReceive(android.content.Context, android.content.Intent);
     method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent);
     method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
+    method public void onUserAdded(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public void onUserRemoved(android.content.Context, android.content.Intent, android.os.UserHandle);
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED";
@@ -6008,6 +6017,7 @@
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
+    method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
     method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
     method public boolean getCameraDisabled(android.content.ComponentName);
     method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
@@ -6040,6 +6050,7 @@
     method public int getPermissionPolicy(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedAccessibilityServices(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedInputMethods(android.content.ComponentName);
+    method public long getRequiredStrongAuthTimeout(android.content.ComponentName);
     method public boolean getScreenCaptureDisabled(android.content.ComponentName);
     method public java.lang.CharSequence getShortSupportMessage(android.content.ComponentName);
     method public boolean getStorageEncryption(android.content.ComponentName);
@@ -6116,6 +6127,7 @@
     method public void setProfileEnabled(android.content.ComponentName);
     method public void setProfileName(android.content.ComponentName, java.lang.String);
     method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
+    method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long);
     method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
     method public void setScreenCaptureDisabled(android.content.ComponentName, boolean);
     method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
@@ -6134,6 +6146,7 @@
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
+    field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE";
     field public static final java.lang.String ACTION_SET_NEW_PARENT_PROFILE_PASSWORD = "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD";
@@ -11979,6 +11992,118 @@
     ctor public ColorMatrixColorFilter(float[]);
   }
 
+  public abstract class ColorSpace {
+    method public static android.graphics.ColorSpace adapt(android.graphics.ColorSpace, float[]);
+    method public static android.graphics.ColorSpace adapt(android.graphics.ColorSpace, float[], android.graphics.ColorSpace.Adaptation);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace, android.graphics.ColorSpace);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace, android.graphics.ColorSpace, android.graphics.ColorSpace.RenderIntent);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace, android.graphics.ColorSpace.RenderIntent);
+    method public float[] fromXyz(float, float, float);
+    method public abstract float[] fromXyz(float[]);
+    method public static android.graphics.ColorSpace get(android.graphics.ColorSpace.Named);
+    method public int getComponentCount();
+    method public int getId();
+    method public abstract float getMaxValue(int);
+    method public abstract float getMinValue(int);
+    method public android.graphics.ColorSpace.Model getModel();
+    method public java.lang.String getName();
+    method public boolean isSrgb();
+    method public abstract boolean isWideGamut();
+    method public float[] toXyz(float, float, float);
+    method public abstract float[] toXyz(float[]);
+    field public static final float[] ILLUMINANT_A;
+    field public static final float[] ILLUMINANT_B;
+    field public static final float[] ILLUMINANT_C;
+    field public static final float[] ILLUMINANT_D50;
+    field public static final float[] ILLUMINANT_D55;
+    field public static final float[] ILLUMINANT_D60;
+    field public static final float[] ILLUMINANT_D65;
+    field public static final float[] ILLUMINANT_D75;
+    field public static final float[] ILLUMINANT_E;
+    field public static final int MAX_ID = 64; // 0x40
+    field public static final int MIN_ID = -1; // 0xffffffff
+  }
+
+  public static final class ColorSpace.Adaptation extends java.lang.Enum {
+    method public static android.graphics.ColorSpace.Adaptation valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.Adaptation[] values();
+    enum_constant public static final android.graphics.ColorSpace.Adaptation BRADFORD;
+    enum_constant public static final android.graphics.ColorSpace.Adaptation VON_KRIES;
+  }
+
+  public static class ColorSpace.Connector {
+    method public android.graphics.ColorSpace getDestination();
+    method public android.graphics.ColorSpace.RenderIntent getIntent();
+    method public android.graphics.ColorSpace getSource();
+    method public float[] transform(float, float, float);
+    method public float[] transform(float[]);
+  }
+
+  public static final class ColorSpace.Model extends java.lang.Enum {
+    method public int getComponentCount();
+    method public static android.graphics.ColorSpace.Model valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.Model[] values();
+    enum_constant public static final android.graphics.ColorSpace.Model CMYK;
+    enum_constant public static final android.graphics.ColorSpace.Model LAB;
+    enum_constant public static final android.graphics.ColorSpace.Model RGB;
+    enum_constant public static final android.graphics.ColorSpace.Model XYZ;
+  }
+
+  public static final class ColorSpace.Named extends java.lang.Enum {
+    method public static android.graphics.ColorSpace.Named valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.Named[] values();
+    enum_constant public static final android.graphics.ColorSpace.Named ACES;
+    enum_constant public static final android.graphics.ColorSpace.Named ACESCG;
+    enum_constant public static final android.graphics.ColorSpace.Named ADOBE_RGB;
+    enum_constant public static final android.graphics.ColorSpace.Named BT2020;
+    enum_constant public static final android.graphics.ColorSpace.Named BT709;
+    enum_constant public static final android.graphics.ColorSpace.Named CIE_LAB;
+    enum_constant public static final android.graphics.ColorSpace.Named CIE_XYZ;
+    enum_constant public static final android.graphics.ColorSpace.Named DCI_P3;
+    enum_constant public static final android.graphics.ColorSpace.Named DISPLAY_P3;
+    enum_constant public static final android.graphics.ColorSpace.Named EXTENDED_SRGB;
+    enum_constant public static final android.graphics.ColorSpace.Named LINEAR_EXTENDED_SRGB;
+    enum_constant public static final android.graphics.ColorSpace.Named LINEAR_SRGB;
+    enum_constant public static final android.graphics.ColorSpace.Named NTSC_1953;
+    enum_constant public static final android.graphics.ColorSpace.Named PRO_PHOTO_RGB;
+    enum_constant public static final android.graphics.ColorSpace.Named SMPTE_C;
+    enum_constant public static final android.graphics.ColorSpace.Named SRGB;
+  }
+
+  public static final class ColorSpace.RenderIntent extends java.lang.Enum {
+    method public static android.graphics.ColorSpace.RenderIntent valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.RenderIntent[] values();
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent ABSOLUTE;
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent PERCEPTUAL;
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent RELATIVE;
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent SATURATION;
+  }
+
+  public static class ColorSpace.Rgb extends android.graphics.ColorSpace {
+    ctor public ColorSpace.Rgb(java.lang.String, float[], java.util.function.DoubleUnaryOperator, java.util.function.DoubleUnaryOperator);
+    ctor public ColorSpace.Rgb(java.lang.String, float[], float[], java.util.function.DoubleUnaryOperator, java.util.function.DoubleUnaryOperator, float, float);
+    method public float[] fromLinear(float, float, float);
+    method public float[] fromLinear(float[]);
+    method public float[] fromXyz(float[]);
+    method public java.util.function.DoubleUnaryOperator getEotf();
+    method public float[] getInverseTransform(float[]);
+    method public float[] getInverseTransform();
+    method public float getMaxValue(int);
+    method public float getMinValue(int);
+    method public java.util.function.DoubleUnaryOperator getOetf();
+    method public float[] getPrimaries(float[]);
+    method public float[] getPrimaries();
+    method public float[] getTransform(float[]);
+    method public float[] getTransform();
+    method public float[] getWhitePoint(float[]);
+    method public float[] getWhitePoint();
+    method public boolean isWideGamut();
+    method public float[] toLinear(float, float, float);
+    method public float[] toLinear(float[]);
+    method public float[] toXyz(float[]);
+  }
+
   public class ComposePathEffect extends android.graphics.PathEffect {
     ctor public ComposePathEffect(android.graphics.PathEffect, android.graphics.PathEffect);
   }
@@ -19922,6 +20047,7 @@
     field public static final int SCO_AUDIO_STATE_CONNECTING = 2; // 0x2
     field public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; // 0x0
     field public static final int SCO_AUDIO_STATE_ERROR = -1; // 0xffffffff
+    field public static final int STREAM_ACCESSIBILITY = 10; // 0xa
     field public static final int STREAM_ALARM = 4; // 0x4
     field public static final int STREAM_DTMF = 8; // 0x8
     field public static final int STREAM_MUSIC = 3; // 0x3
@@ -34978,6 +35104,8 @@
     method public final void requestUnbind();
     method public final void setNotificationsShown(java.lang.String[]);
     method public final void snoozeNotification(java.lang.String, long);
+    method public final void snoozeNotification(java.lang.String);
+    method public final void unsnoozeNotification(java.lang.String);
     field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -37637,6 +37765,7 @@
     method public int getCallState();
     method public android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
+    method public boolean getDataEnabled();
     method public int getDataNetworkType();
     method public int getDataState();
     method public java.lang.String getDeviceId();
@@ -37683,6 +37812,7 @@
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
+    method public void setDataEnabled(boolean);
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
     method public boolean setOperatorBrandOverride(java.lang.String);
     method public boolean setPreferredNetworkTypeToGlobal();
@@ -37690,6 +37820,7 @@
     field public static final java.lang.String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL";
     field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
     field public static final java.lang.String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
+    field public static final java.lang.String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
     field public static final int APPTYPE_CSIM = 4; // 0x4
     field public static final int APPTYPE_ISIM = 5; // 0x5
     field public static final int APPTYPE_RUIM = 3; // 0x3
@@ -37709,11 +37840,15 @@
     field public static final int DATA_CONNECTING = 1; // 0x1
     field public static final int DATA_DISCONNECTED = 0; // 0x0
     field public static final int DATA_SUSPENDED = 3; // 0x3
+    field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
     field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
+    field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
+    field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
     field public static final java.lang.String EXTRA_STATE = "state";
     field public static final java.lang.String EXTRA_STATE_IDLE;
     field public static final java.lang.String EXTRA_STATE_OFFHOOK;
     field public static final java.lang.String EXTRA_STATE_RINGING;
+    field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
     field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
     field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
     field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -38987,6 +39122,16 @@
     method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
   }
 
+  public abstract interface TextAssistant {
+    method public abstract void addLinks(android.text.Spannable, int);
+    method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+  }
+
+  public class TextClassification {
+    ctor public TextClassification();
+    method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
+  }
+
   public abstract interface TextDirectionHeuristic {
     method public abstract boolean isRtl(char[], int, int);
     method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -39014,6 +39159,13 @@
     field public int linkColor;
   }
 
+  public class TextSelection {
+    ctor public TextSelection();
+    method public int getSelectionEndIndex();
+    method public int getSelectionStartIndex();
+    method public android.text.TextClassification getTextClassification();
+  }
+
   public class TextUtils {
     method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
     method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -42835,6 +42987,7 @@
     method public java.lang.Object getTag(int);
     method public int getTextAlignment();
     method public int getTextDirection();
+    method public final java.lang.CharSequence getTooltip();
     method public final int getTop();
     method protected float getTopFadingEdgeStrength();
     method protected int getTopPaddingOffset();
@@ -43123,6 +43276,7 @@
     method public void setTag(int, java.lang.Object);
     method public void setTextAlignment(int);
     method public void setTextDirection(int);
+    method public final void setTooltip(java.lang.CharSequence);
     method public final void setTop(int);
     method public void setTouchDelegate(android.view.TouchDelegate);
     method public final void setTransitionName(java.lang.String);
@@ -48484,6 +48638,7 @@
     method public float getShadowRadius();
     method public final boolean getShowSoftInputOnFocus();
     method public java.lang.CharSequence getText();
+    method public android.text.TextAssistant getTextAssistant();
     method public final android.content.res.ColorStateList getTextColors();
     method public java.util.Locale getTextLocale();
     method public android.os.LocaleList getTextLocales();
@@ -48594,6 +48749,7 @@
     method public final void setText(int, android.widget.TextView.BufferType);
     method public void setTextAppearance(int);
     method public deprecated void setTextAppearance(android.content.Context, int);
+    method public void setTextAssistant(android.text.TextAssistant);
     method public void setTextColor(int);
     method public void setTextColor(android.content.res.ColorStateList);
     method public void setTextIsSelectable(boolean);
@@ -48608,6 +48764,8 @@
     method public void setTypeface(android.graphics.Typeface, int);
     method public void setTypeface(android.graphics.Typeface);
     method public void setWidth(int);
+    field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0
+    field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1
   }
 
   public static final class TextView.BufferType extends java.lang.Enum {
diff --git a/api/system-current.txt b/api/system-current.txt
index 60d98a7..613f387 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -396,6 +396,10 @@
     field public static final int autoLink = 16842928; // 0x10100b0
     field public static final int autoMirrored = 16843754; // 0x10103ea
     field public static final int autoRemoveFromRecents = 16843847; // 0x1010447
+    field public static final int autoSizeMinTextSize = 16844088; // 0x1010538
+    field public static final int autoSizeStepGranularity = 16844086; // 0x1010536
+    field public static final int autoSizeStepSizeSet = 16844087; // 0x1010537
+    field public static final int autoSizeText = 16844085; // 0x1010535
     field public static final int autoStart = 16843445; // 0x10102b5
     field public static final deprecated int autoText = 16843114; // 0x101016a
     field public static final int autoUrlDetect = 16843404; // 0x101028c
@@ -1468,6 +1472,7 @@
     field public static final int toYDelta = 16843209; // 0x10101c9
     field public static final int toYScale = 16843205; // 0x10101c5
     field public static final int toolbarStyle = 16843946; // 0x10104aa
+    field public static final int tooltip = 16844084; // 0x1010534
     field public static final int top = 16843182; // 0x10101ae
     field public static final int topBright = 16842955; // 0x10100cb
     field public static final int topDark = 16842951; // 0x10100c7
@@ -3613,6 +3618,7 @@
     method public int getRequestedOrientation();
     method public final android.view.SearchEvent getSearchEvent();
     method public int getTaskId();
+    method public android.text.TextAssistant getTextAssistant();
     method public final java.lang.CharSequence getTitle();
     method public final int getTitleColor();
     method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3764,6 +3770,7 @@
     method public final void setResult(int, android.content.Intent);
     method public final deprecated void setSecondaryProgress(int);
     method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
+    method public void setTextAssistant(android.text.TextAssistant);
     method public void setTitle(java.lang.CharSequence);
     method public void setTitle(int);
     method public deprecated void setTitleColor(int);
@@ -6143,6 +6150,8 @@
     method public void onReceive(android.content.Context, android.content.Intent);
     method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent);
     method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
+    method public void onUserAdded(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public void onUserRemoved(android.content.Context, android.content.Intent, android.os.UserHandle);
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED";
@@ -6180,6 +6189,7 @@
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
+    method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
     method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
     method public boolean getCameraDisabled(android.content.ComponentName);
     method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
@@ -6220,6 +6230,7 @@
     method public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser();
     method public android.content.ComponentName getProfileOwner() throws java.lang.IllegalArgumentException;
     method public java.lang.String getProfileOwnerNameAsUser(int) throws java.lang.IllegalArgumentException;
+    method public long getRequiredStrongAuthTimeout(android.content.ComponentName);
     method public boolean getScreenCaptureDisabled(android.content.ComponentName);
     method public java.lang.CharSequence getShortSupportMessage(android.content.ComponentName);
     method public boolean getStorageEncryption(android.content.ComponentName);
@@ -6299,6 +6310,7 @@
     method public void setProfileEnabled(android.content.ComponentName);
     method public void setProfileName(android.content.ComponentName, java.lang.String);
     method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
+    method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long);
     method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
     method public void setScreenCaptureDisabled(android.content.ComponentName, boolean);
     method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
@@ -6317,6 +6329,7 @@
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
+    field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
     field public static final java.lang.String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
@@ -12462,6 +12475,118 @@
     ctor public ColorMatrixColorFilter(float[]);
   }
 
+  public abstract class ColorSpace {
+    method public static android.graphics.ColorSpace adapt(android.graphics.ColorSpace, float[]);
+    method public static android.graphics.ColorSpace adapt(android.graphics.ColorSpace, float[], android.graphics.ColorSpace.Adaptation);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace, android.graphics.ColorSpace);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace, android.graphics.ColorSpace, android.graphics.ColorSpace.RenderIntent);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace, android.graphics.ColorSpace.RenderIntent);
+    method public float[] fromXyz(float, float, float);
+    method public abstract float[] fromXyz(float[]);
+    method public static android.graphics.ColorSpace get(android.graphics.ColorSpace.Named);
+    method public int getComponentCount();
+    method public int getId();
+    method public abstract float getMaxValue(int);
+    method public abstract float getMinValue(int);
+    method public android.graphics.ColorSpace.Model getModel();
+    method public java.lang.String getName();
+    method public boolean isSrgb();
+    method public abstract boolean isWideGamut();
+    method public float[] toXyz(float, float, float);
+    method public abstract float[] toXyz(float[]);
+    field public static final float[] ILLUMINANT_A;
+    field public static final float[] ILLUMINANT_B;
+    field public static final float[] ILLUMINANT_C;
+    field public static final float[] ILLUMINANT_D50;
+    field public static final float[] ILLUMINANT_D55;
+    field public static final float[] ILLUMINANT_D60;
+    field public static final float[] ILLUMINANT_D65;
+    field public static final float[] ILLUMINANT_D75;
+    field public static final float[] ILLUMINANT_E;
+    field public static final int MAX_ID = 64; // 0x40
+    field public static final int MIN_ID = -1; // 0xffffffff
+  }
+
+  public static final class ColorSpace.Adaptation extends java.lang.Enum {
+    method public static android.graphics.ColorSpace.Adaptation valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.Adaptation[] values();
+    enum_constant public static final android.graphics.ColorSpace.Adaptation BRADFORD;
+    enum_constant public static final android.graphics.ColorSpace.Adaptation VON_KRIES;
+  }
+
+  public static class ColorSpace.Connector {
+    method public android.graphics.ColorSpace getDestination();
+    method public android.graphics.ColorSpace.RenderIntent getIntent();
+    method public android.graphics.ColorSpace getSource();
+    method public float[] transform(float, float, float);
+    method public float[] transform(float[]);
+  }
+
+  public static final class ColorSpace.Model extends java.lang.Enum {
+    method public int getComponentCount();
+    method public static android.graphics.ColorSpace.Model valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.Model[] values();
+    enum_constant public static final android.graphics.ColorSpace.Model CMYK;
+    enum_constant public static final android.graphics.ColorSpace.Model LAB;
+    enum_constant public static final android.graphics.ColorSpace.Model RGB;
+    enum_constant public static final android.graphics.ColorSpace.Model XYZ;
+  }
+
+  public static final class ColorSpace.Named extends java.lang.Enum {
+    method public static android.graphics.ColorSpace.Named valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.Named[] values();
+    enum_constant public static final android.graphics.ColorSpace.Named ACES;
+    enum_constant public static final android.graphics.ColorSpace.Named ACESCG;
+    enum_constant public static final android.graphics.ColorSpace.Named ADOBE_RGB;
+    enum_constant public static final android.graphics.ColorSpace.Named BT2020;
+    enum_constant public static final android.graphics.ColorSpace.Named BT709;
+    enum_constant public static final android.graphics.ColorSpace.Named CIE_LAB;
+    enum_constant public static final android.graphics.ColorSpace.Named CIE_XYZ;
+    enum_constant public static final android.graphics.ColorSpace.Named DCI_P3;
+    enum_constant public static final android.graphics.ColorSpace.Named DISPLAY_P3;
+    enum_constant public static final android.graphics.ColorSpace.Named EXTENDED_SRGB;
+    enum_constant public static final android.graphics.ColorSpace.Named LINEAR_EXTENDED_SRGB;
+    enum_constant public static final android.graphics.ColorSpace.Named LINEAR_SRGB;
+    enum_constant public static final android.graphics.ColorSpace.Named NTSC_1953;
+    enum_constant public static final android.graphics.ColorSpace.Named PRO_PHOTO_RGB;
+    enum_constant public static final android.graphics.ColorSpace.Named SMPTE_C;
+    enum_constant public static final android.graphics.ColorSpace.Named SRGB;
+  }
+
+  public static final class ColorSpace.RenderIntent extends java.lang.Enum {
+    method public static android.graphics.ColorSpace.RenderIntent valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.RenderIntent[] values();
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent ABSOLUTE;
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent PERCEPTUAL;
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent RELATIVE;
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent SATURATION;
+  }
+
+  public static class ColorSpace.Rgb extends android.graphics.ColorSpace {
+    ctor public ColorSpace.Rgb(java.lang.String, float[], java.util.function.DoubleUnaryOperator, java.util.function.DoubleUnaryOperator);
+    ctor public ColorSpace.Rgb(java.lang.String, float[], float[], java.util.function.DoubleUnaryOperator, java.util.function.DoubleUnaryOperator, float, float);
+    method public float[] fromLinear(float, float, float);
+    method public float[] fromLinear(float[]);
+    method public float[] fromXyz(float[]);
+    method public java.util.function.DoubleUnaryOperator getEotf();
+    method public float[] getInverseTransform(float[]);
+    method public float[] getInverseTransform();
+    method public float getMaxValue(int);
+    method public float getMinValue(int);
+    method public java.util.function.DoubleUnaryOperator getOetf();
+    method public float[] getPrimaries(float[]);
+    method public float[] getPrimaries();
+    method public float[] getTransform(float[]);
+    method public float[] getTransform();
+    method public float[] getWhitePoint(float[]);
+    method public float[] getWhitePoint();
+    method public boolean isWideGamut();
+    method public float[] toLinear(float, float, float);
+    method public float[] toLinear(float[]);
+    method public float[] toXyz(float[]);
+  }
+
   public class ComposePathEffect extends android.graphics.PathEffect {
     ctor public ComposePathEffect(android.graphics.PathEffect, android.graphics.PathEffect);
   }
@@ -21484,6 +21609,7 @@
     field public static final int SCO_AUDIO_STATE_CONNECTING = 2; // 0x2
     field public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; // 0x0
     field public static final int SCO_AUDIO_STATE_ERROR = -1; // 0xffffffff
+    field public static final int STREAM_ACCESSIBILITY = 10; // 0xa
     field public static final int STREAM_ALARM = 4; // 0x4
     field public static final int STREAM_DTMF = 8; // 0x8
     field public static final int STREAM_MUSIC = 3; // 0x3
@@ -35388,6 +35514,7 @@
     field public static final java.lang.String ACTION_VR_LISTENER_SETTINGS = "android.settings.VR_LISTENER_SETTINGS";
     field public static final java.lang.String ACTION_WEBVIEW_SETTINGS = "android.settings.WEBVIEW_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_IP_SETTINGS = "android.settings.WIFI_IP_SETTINGS";
+    field public static final java.lang.String ACTION_WIFI_SAVED_NETWORK_SETTINGS = "android.settings.WIFI_SAVED_NETWORK_SETTINGS";
     field public static final java.lang.String ACTION_WIFI_SETTINGS = "android.settings.WIFI_SETTINGS";
     field public static final java.lang.String ACTION_WIRELESS_SETTINGS = "android.settings.WIRELESS_SETTINGS";
     field public static final java.lang.String AUTHORITY = "settings";
@@ -37776,7 +37903,9 @@
     method public final void setNotificationsShown(java.lang.String[]);
     method public final void setOnNotificationPostedTrim(int);
     method public final void snoozeNotification(java.lang.String, long);
+    method public final void snoozeNotification(java.lang.String);
     method public void unregisterAsSystemService() throws android.os.RemoteException;
+    method public final void unsnoozeNotification(java.lang.String);
     field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -42155,6 +42284,16 @@
     method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
   }
 
+  public abstract interface TextAssistant {
+    method public abstract void addLinks(android.text.Spannable, int);
+    method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+  }
+
+  public class TextClassification {
+    ctor public TextClassification();
+    method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
+  }
+
   public abstract interface TextDirectionHeuristic {
     method public abstract boolean isRtl(char[], int, int);
     method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -42182,6 +42321,13 @@
     field public int linkColor;
   }
 
+  public class TextSelection {
+    ctor public TextSelection();
+    method public int getSelectionEndIndex();
+    method public int getSelectionStartIndex();
+    method public android.text.TextClassification getTextClassification();
+  }
+
   public class TextUtils {
     method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
     method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -46003,6 +46149,7 @@
     method public java.lang.Object getTag(int);
     method public int getTextAlignment();
     method public int getTextDirection();
+    method public final java.lang.CharSequence getTooltip();
     method public final int getTop();
     method protected float getTopFadingEdgeStrength();
     method protected int getTopPaddingOffset();
@@ -46291,6 +46438,7 @@
     method public void setTag(int, java.lang.Object);
     method public void setTextAlignment(int);
     method public void setTextDirection(int);
+    method public final void setTooltip(java.lang.CharSequence);
     method public final void setTop(int);
     method public void setTouchDelegate(android.view.TouchDelegate);
     method public final void setTransitionName(java.lang.String);
@@ -52011,6 +52159,7 @@
     method public float getShadowRadius();
     method public final boolean getShowSoftInputOnFocus();
     method public java.lang.CharSequence getText();
+    method public android.text.TextAssistant getTextAssistant();
     method public final android.content.res.ColorStateList getTextColors();
     method public java.util.Locale getTextLocale();
     method public android.os.LocaleList getTextLocales();
@@ -52121,6 +52270,7 @@
     method public final void setText(int, android.widget.TextView.BufferType);
     method public void setTextAppearance(int);
     method public deprecated void setTextAppearance(android.content.Context, int);
+    method public void setTextAssistant(android.text.TextAssistant);
     method public void setTextColor(int);
     method public void setTextColor(android.content.res.ColorStateList);
     method public void setTextIsSelectable(boolean);
@@ -52135,6 +52285,8 @@
     method public void setTypeface(android.graphics.Typeface, int);
     method public void setTypeface(android.graphics.Typeface);
     method public void setWidth(int);
+    field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0
+    field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1
   }
 
   public static final class TextView.BufferType extends java.lang.Enum {
diff --git a/api/test-current.txt b/api/test-current.txt
index be55a94..b321ae6 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -289,6 +289,10 @@
     field public static final int autoLink = 16842928; // 0x10100b0
     field public static final int autoMirrored = 16843754; // 0x10103ea
     field public static final int autoRemoveFromRecents = 16843847; // 0x1010447
+    field public static final int autoSizeMinTextSize = 16844088; // 0x1010538
+    field public static final int autoSizeStepGranularity = 16844086; // 0x1010536
+    field public static final int autoSizeStepSizeSet = 16844087; // 0x1010537
+    field public static final int autoSizeText = 16844085; // 0x1010535
     field public static final int autoStart = 16843445; // 0x10102b5
     field public static final deprecated int autoText = 16843114; // 0x101016a
     field public static final int autoUrlDetect = 16843404; // 0x101028c
@@ -1357,6 +1361,7 @@
     field public static final int toYDelta = 16843209; // 0x10101c9
     field public static final int toYScale = 16843205; // 0x10101c5
     field public static final int toolbarStyle = 16843946; // 0x10104aa
+    field public static final int tooltip = 16844084; // 0x1010534
     field public static final int top = 16843182; // 0x10101ae
     field public static final int topBright = 16842955; // 0x10100cb
     field public static final int topDark = 16842951; // 0x10100c7
@@ -3498,6 +3503,7 @@
     method public int getRequestedOrientation();
     method public final android.view.SearchEvent getSearchEvent();
     method public int getTaskId();
+    method public android.text.TextAssistant getTextAssistant();
     method public final java.lang.CharSequence getTitle();
     method public final int getTitleColor();
     method public android.app.VoiceInteractor getVoiceInteractor();
@@ -3647,6 +3653,7 @@
     method public final void setResult(int, android.content.Intent);
     method public final deprecated void setSecondaryProgress(int);
     method public void setTaskDescription(android.app.ActivityManager.TaskDescription);
+    method public void setTextAssistant(android.text.TextAssistant);
     method public void setTitle(java.lang.CharSequence);
     method public void setTitle(int);
     method public deprecated void setTitleColor(int);
@@ -5396,6 +5403,7 @@
     method public android.app.AutomaticZenRule getAutomaticZenRule(java.lang.String);
     method public java.util.Map<java.lang.String, android.app.AutomaticZenRule> getAutomaticZenRules();
     method public final int getCurrentInterruptionFilter();
+    method public android.content.ComponentName getEffectsSuppressor();
     method public int getImportance();
     method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
@@ -5987,6 +5995,8 @@
     method public void onReceive(android.content.Context, android.content.Intent);
     method public void onSecurityLogsAvailable(android.content.Context, android.content.Intent);
     method public void onSystemUpdatePending(android.content.Context, android.content.Intent, long);
+    method public void onUserAdded(android.content.Context, android.content.Intent, android.os.UserHandle);
+    method public void onUserRemoved(android.content.Context, android.content.Intent, android.os.UserHandle);
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLED = "android.app.action.DEVICE_ADMIN_DISABLED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_DISABLE_REQUESTED = "android.app.action.DEVICE_ADMIN_DISABLE_REQUESTED";
     field public static final java.lang.String ACTION_DEVICE_ADMIN_ENABLED = "android.app.action.DEVICE_ADMIN_ENABLED";
@@ -6024,6 +6034,7 @@
     method public android.os.Bundle getApplicationRestrictions(android.content.ComponentName, java.lang.String);
     method public java.lang.String getApplicationRestrictionsManagingPackage(android.content.ComponentName);
     method public boolean getAutoTimeRequired();
+    method public java.util.List<android.os.UserHandle> getBindDeviceAdminTargetUsers(android.content.ComponentName);
     method public boolean getBluetoothContactSharingDisabled(android.content.ComponentName);
     method public boolean getCameraDisabled(android.content.ComponentName);
     method public java.lang.String getCertInstallerPackage(android.content.ComponentName) throws java.lang.SecurityException;
@@ -6034,6 +6045,9 @@
     method public java.lang.CharSequence getDeviceOwnerLockScreenInfo();
     method public java.util.List<byte[]> getInstalledCaCerts(android.content.ComponentName);
     method public int getKeyguardDisabledFeatures(android.content.ComponentName);
+    method public long getLastBugReportRequestTime();
+    method public long getLastNetworkLogRetrievalTime();
+    method public long getLastSecurityLogRetrievalTime();
     method public java.lang.CharSequence getLongSupportMessage(android.content.ComponentName);
     method public int getMaximumFailedPasswordsForWipe(android.content.ComponentName);
     method public long getMaximumTimeToLock(android.content.ComponentName);
@@ -6056,6 +6070,7 @@
     method public int getPermissionPolicy(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedAccessibilityServices(android.content.ComponentName);
     method public java.util.List<java.lang.String> getPermittedInputMethods(android.content.ComponentName);
+    method public long getRequiredStrongAuthTimeout(android.content.ComponentName);
     method public boolean getScreenCaptureDisabled(android.content.ComponentName);
     method public java.lang.CharSequence getShortSupportMessage(android.content.ComponentName);
     method public boolean getStorageEncryption(android.content.ComponentName);
@@ -6132,6 +6147,7 @@
     method public void setProfileEnabled(android.content.ComponentName);
     method public void setProfileName(android.content.ComponentName, java.lang.String);
     method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
+    method public void setRequiredStrongAuthTimeout(android.content.ComponentName, long);
     method public void setRestrictionsProvider(android.content.ComponentName, android.content.ComponentName);
     method public void setScreenCaptureDisabled(android.content.ComponentName, boolean);
     method public void setSecureSetting(android.content.ComponentName, java.lang.String, java.lang.String);
@@ -6150,6 +6166,7 @@
     field public static final java.lang.String ACTION_ADD_DEVICE_ADMIN = "android.app.action.ADD_DEVICE_ADMIN";
     field public static final java.lang.String ACTION_DEVICE_OWNER_CHANGED = "android.app.action.DEVICE_OWNER_CHANGED";
     field public static final java.lang.String ACTION_MANAGED_PROFILE_PROVISIONED = "android.app.action.MANAGED_PROFILE_PROVISIONED";
+    field public static final java.lang.String ACTION_PROVISIONING_SUCCESSFUL = "android.app.action.PROVISIONING_SUCCESSFUL";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_DEVICE = "android.app.action.PROVISION_MANAGED_DEVICE";
     field public static final java.lang.String ACTION_PROVISION_MANAGED_PROFILE = "android.app.action.PROVISION_MANAGED_PROFILE";
     field public static final java.lang.String ACTION_SET_NEW_PARENT_PROFILE_PASSWORD = "android.app.action.SET_NEW_PARENT_PROFILE_PASSWORD";
@@ -12004,6 +12021,118 @@
     ctor public ColorMatrixColorFilter(float[]);
   }
 
+  public abstract class ColorSpace {
+    method public static android.graphics.ColorSpace adapt(android.graphics.ColorSpace, float[]);
+    method public static android.graphics.ColorSpace adapt(android.graphics.ColorSpace, float[], android.graphics.ColorSpace.Adaptation);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace, android.graphics.ColorSpace);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace, android.graphics.ColorSpace, android.graphics.ColorSpace.RenderIntent);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace);
+    method public static android.graphics.ColorSpace.Connector connect(android.graphics.ColorSpace, android.graphics.ColorSpace.RenderIntent);
+    method public float[] fromXyz(float, float, float);
+    method public abstract float[] fromXyz(float[]);
+    method public static android.graphics.ColorSpace get(android.graphics.ColorSpace.Named);
+    method public int getComponentCount();
+    method public int getId();
+    method public abstract float getMaxValue(int);
+    method public abstract float getMinValue(int);
+    method public android.graphics.ColorSpace.Model getModel();
+    method public java.lang.String getName();
+    method public boolean isSrgb();
+    method public abstract boolean isWideGamut();
+    method public float[] toXyz(float, float, float);
+    method public abstract float[] toXyz(float[]);
+    field public static final float[] ILLUMINANT_A;
+    field public static final float[] ILLUMINANT_B;
+    field public static final float[] ILLUMINANT_C;
+    field public static final float[] ILLUMINANT_D50;
+    field public static final float[] ILLUMINANT_D55;
+    field public static final float[] ILLUMINANT_D60;
+    field public static final float[] ILLUMINANT_D65;
+    field public static final float[] ILLUMINANT_D75;
+    field public static final float[] ILLUMINANT_E;
+    field public static final int MAX_ID = 64; // 0x40
+    field public static final int MIN_ID = -1; // 0xffffffff
+  }
+
+  public static final class ColorSpace.Adaptation extends java.lang.Enum {
+    method public static android.graphics.ColorSpace.Adaptation valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.Adaptation[] values();
+    enum_constant public static final android.graphics.ColorSpace.Adaptation BRADFORD;
+    enum_constant public static final android.graphics.ColorSpace.Adaptation VON_KRIES;
+  }
+
+  public static class ColorSpace.Connector {
+    method public android.graphics.ColorSpace getDestination();
+    method public android.graphics.ColorSpace.RenderIntent getIntent();
+    method public android.graphics.ColorSpace getSource();
+    method public float[] transform(float, float, float);
+    method public float[] transform(float[]);
+  }
+
+  public static final class ColorSpace.Model extends java.lang.Enum {
+    method public int getComponentCount();
+    method public static android.graphics.ColorSpace.Model valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.Model[] values();
+    enum_constant public static final android.graphics.ColorSpace.Model CMYK;
+    enum_constant public static final android.graphics.ColorSpace.Model LAB;
+    enum_constant public static final android.graphics.ColorSpace.Model RGB;
+    enum_constant public static final android.graphics.ColorSpace.Model XYZ;
+  }
+
+  public static final class ColorSpace.Named extends java.lang.Enum {
+    method public static android.graphics.ColorSpace.Named valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.Named[] values();
+    enum_constant public static final android.graphics.ColorSpace.Named ACES;
+    enum_constant public static final android.graphics.ColorSpace.Named ACESCG;
+    enum_constant public static final android.graphics.ColorSpace.Named ADOBE_RGB;
+    enum_constant public static final android.graphics.ColorSpace.Named BT2020;
+    enum_constant public static final android.graphics.ColorSpace.Named BT709;
+    enum_constant public static final android.graphics.ColorSpace.Named CIE_LAB;
+    enum_constant public static final android.graphics.ColorSpace.Named CIE_XYZ;
+    enum_constant public static final android.graphics.ColorSpace.Named DCI_P3;
+    enum_constant public static final android.graphics.ColorSpace.Named DISPLAY_P3;
+    enum_constant public static final android.graphics.ColorSpace.Named EXTENDED_SRGB;
+    enum_constant public static final android.graphics.ColorSpace.Named LINEAR_EXTENDED_SRGB;
+    enum_constant public static final android.graphics.ColorSpace.Named LINEAR_SRGB;
+    enum_constant public static final android.graphics.ColorSpace.Named NTSC_1953;
+    enum_constant public static final android.graphics.ColorSpace.Named PRO_PHOTO_RGB;
+    enum_constant public static final android.graphics.ColorSpace.Named SMPTE_C;
+    enum_constant public static final android.graphics.ColorSpace.Named SRGB;
+  }
+
+  public static final class ColorSpace.RenderIntent extends java.lang.Enum {
+    method public static android.graphics.ColorSpace.RenderIntent valueOf(java.lang.String);
+    method public static final android.graphics.ColorSpace.RenderIntent[] values();
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent ABSOLUTE;
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent PERCEPTUAL;
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent RELATIVE;
+    enum_constant public static final android.graphics.ColorSpace.RenderIntent SATURATION;
+  }
+
+  public static class ColorSpace.Rgb extends android.graphics.ColorSpace {
+    ctor public ColorSpace.Rgb(java.lang.String, float[], java.util.function.DoubleUnaryOperator, java.util.function.DoubleUnaryOperator);
+    ctor public ColorSpace.Rgb(java.lang.String, float[], float[], java.util.function.DoubleUnaryOperator, java.util.function.DoubleUnaryOperator, float, float);
+    method public float[] fromLinear(float, float, float);
+    method public float[] fromLinear(float[]);
+    method public float[] fromXyz(float[]);
+    method public java.util.function.DoubleUnaryOperator getEotf();
+    method public float[] getInverseTransform(float[]);
+    method public float[] getInverseTransform();
+    method public float getMaxValue(int);
+    method public float getMinValue(int);
+    method public java.util.function.DoubleUnaryOperator getOetf();
+    method public float[] getPrimaries(float[]);
+    method public float[] getPrimaries();
+    method public float[] getTransform(float[]);
+    method public float[] getTransform();
+    method public float[] getWhitePoint(float[]);
+    method public float[] getWhitePoint();
+    method public boolean isWideGamut();
+    method public float[] toLinear(float, float, float);
+    method public float[] toLinear(float[]);
+    method public float[] toXyz(float[]);
+  }
+
   public class ComposePathEffect extends android.graphics.PathEffect {
     ctor public ComposePathEffect(android.graphics.PathEffect, android.graphics.PathEffect);
   }
@@ -20003,6 +20132,7 @@
     field public static final int SCO_AUDIO_STATE_CONNECTING = 2; // 0x2
     field public static final int SCO_AUDIO_STATE_DISCONNECTED = 0; // 0x0
     field public static final int SCO_AUDIO_STATE_ERROR = -1; // 0xffffffff
+    field public static final int STREAM_ACCESSIBILITY = 10; // 0xa
     field public static final int STREAM_ALARM = 4; // 0x4
     field public static final int STREAM_DTMF = 8; // 0x8
     field public static final int STREAM_MUSIC = 3; // 0x3
@@ -35068,6 +35198,8 @@
     method public final void requestUnbind();
     method public final void setNotificationsShown(java.lang.String[]);
     method public final void snoozeNotification(java.lang.String, long);
+    method public final void snoozeNotification(java.lang.String);
+    method public final void unsnoozeNotification(java.lang.String);
     field public static final int HINT_HOST_DISABLE_CALL_EFFECTS = 4; // 0x4
     field public static final int HINT_HOST_DISABLE_EFFECTS = 1; // 0x1
     field public static final int HINT_HOST_DISABLE_NOTIFICATION_EFFECTS = 2; // 0x2
@@ -37727,6 +37859,7 @@
     method public int getCallState();
     method public android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
+    method public boolean getDataEnabled();
     method public int getDataNetworkType();
     method public int getDataState();
     method public java.lang.String getDeviceId();
@@ -37773,6 +37906,7 @@
     method public java.lang.String sendEnvelopeWithStatus(java.lang.String);
     method public void sendUssdRequest(java.lang.String, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
     method public void sendUssdRequest(java.lang.String, int, android.telephony.TelephonyManager.OnReceiveUssdResponseCallback, android.os.Handler);
+    method public void setDataEnabled(boolean);
     method public boolean setLine1NumberForDisplay(java.lang.String, java.lang.String);
     method public boolean setOperatorBrandOverride(java.lang.String);
     method public boolean setPreferredNetworkTypeToGlobal();
@@ -37780,6 +37914,7 @@
     field public static final java.lang.String ACTION_CONFIGURE_VOICEMAIL = "android.telephony.action.CONFIGURE_VOICEMAIL";
     field public static final java.lang.String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE";
     field public static final java.lang.String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE";
+    field public static final java.lang.String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
     field public static final int APPTYPE_CSIM = 4; // 0x4
     field public static final int APPTYPE_ISIM = 5; // 0x5
     field public static final int APPTYPE_RUIM = 3; // 0x3
@@ -37799,11 +37934,15 @@
     field public static final int DATA_CONNECTING = 1; // 0x1
     field public static final int DATA_DISCONNECTED = 0; // 0x0
     field public static final int DATA_SUSPENDED = 3; // 0x3
+    field public static final java.lang.String EXTRA_CALL_VOICEMAIL_INTENT = "android.telephony.extra.CALL_VOICEMAIL_INTENT";
     field public static final java.lang.String EXTRA_INCOMING_NUMBER = "incoming_number";
+    field public static final java.lang.String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT = "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
+    field public static final java.lang.String EXTRA_NOTIFICATION_COUNT = "android.telephony.extra.NOTIFICATION_COUNT";
     field public static final java.lang.String EXTRA_STATE = "state";
     field public static final java.lang.String EXTRA_STATE_IDLE;
     field public static final java.lang.String EXTRA_STATE_OFFHOOK;
     field public static final java.lang.String EXTRA_STATE_RINGING;
+    field public static final java.lang.String EXTRA_VOICEMAIL_NUMBER = "android.telephony.extra.VOICEMAIL_NUMBER";
     field public static final int NETWORK_TYPE_1xRTT = 7; // 0x7
     field public static final int NETWORK_TYPE_CDMA = 4; // 0x4
     field public static final int NETWORK_TYPE_EDGE = 2; // 0x2
@@ -39079,6 +39218,16 @@
     method public android.text.StaticLayout.Builder setTextDirection(android.text.TextDirectionHeuristic);
   }
 
+  public abstract interface TextAssistant {
+    method public abstract void addLinks(android.text.Spannable, int);
+    method public abstract android.text.TextSelection suggestSelection(java.lang.CharSequence, int, int);
+  }
+
+  public class TextClassification {
+    ctor public TextClassification();
+    method public java.util.Map<java.lang.String, java.lang.Float> getTypeConfidence();
+  }
+
   public abstract interface TextDirectionHeuristic {
     method public abstract boolean isRtl(char[], int, int);
     method public abstract boolean isRtl(java.lang.CharSequence, int, int);
@@ -39106,6 +39255,13 @@
     field public int linkColor;
   }
 
+  public class TextSelection {
+    ctor public TextSelection();
+    method public int getSelectionEndIndex();
+    method public int getSelectionStartIndex();
+    method public android.text.TextClassification getTextClassification();
+  }
+
   public class TextUtils {
     method public static deprecated java.lang.CharSequence commaEllipsize(java.lang.CharSequence, android.text.TextPaint, float, java.lang.String, java.lang.String);
     method public static java.lang.CharSequence concat(java.lang.CharSequence...);
@@ -43080,6 +43236,8 @@
     method public java.lang.Object getTag(int);
     method public int getTextAlignment();
     method public int getTextDirection();
+    method public final java.lang.CharSequence getTooltip();
+    method public android.view.View getTooltipView();
     method public final int getTop();
     method protected float getTopFadingEdgeStrength();
     method protected int getTopPaddingOffset();
@@ -43368,6 +43526,7 @@
     method public void setTag(int, java.lang.Object);
     method public void setTextAlignment(int);
     method public void setTextDirection(int);
+    method public final void setTooltip(java.lang.CharSequence);
     method public final void setTop(int);
     method public void setTouchDelegate(android.view.TouchDelegate);
     method public final void setTransitionName(java.lang.String);
@@ -43650,10 +43809,14 @@
     method public static deprecated int getEdgeSlop();
     method public static deprecated int getFadingEdgeLength();
     method public static deprecated long getGlobalActionKeyTimeout();
+    method public static int getHoverTooltipHideShortTimeout();
+    method public static int getHoverTooltipHideTimeout();
+    method public static int getHoverTooltipShowTimeout();
     method public static int getJumpTapTimeout();
     method public static int getKeyRepeatDelay();
     method public static int getKeyRepeatTimeout();
     method public static int getLongPressTimeout();
+    method public static int getLongPressTooltipHideTimeout();
     method public static deprecated int getMaximumDrawingCacheSize();
     method public static deprecated int getMaximumFlingVelocity();
     method public static deprecated int getMinimumFlingVelocity();
@@ -48737,6 +48900,7 @@
     method public float getShadowRadius();
     method public final boolean getShowSoftInputOnFocus();
     method public java.lang.CharSequence getText();
+    method public android.text.TextAssistant getTextAssistant();
     method public final android.content.res.ColorStateList getTextColors();
     method public java.util.Locale getTextLocale();
     method public android.os.LocaleList getTextLocales();
@@ -48847,6 +49011,7 @@
     method public final void setText(int, android.widget.TextView.BufferType);
     method public void setTextAppearance(int);
     method public deprecated void setTextAppearance(android.content.Context, int);
+    method public void setTextAssistant(android.text.TextAssistant);
     method public void setTextColor(int);
     method public void setTextColor(android.content.res.ColorStateList);
     method public void setTextIsSelectable(boolean);
@@ -48861,6 +49026,8 @@
     method public void setTypeface(android.graphics.Typeface, int);
     method public void setTypeface(android.graphics.Typeface);
     method public void setWidth(int);
+    field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0
+    field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1
   }
 
   public static final class TextView.BufferType extends java.lang.Enum {
diff --git a/core/java/android/annotation/ColorLong.java b/core/java/android/annotation/ColorLong.java
new file mode 100644
index 0000000..9b19c76
--- /dev/null
+++ b/core/java/android/annotation/ColorLong.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * <p>Denotes that the annotated element represents a packed color
+ * long. If applied to a long array, every element in the array
+ * represents a color long. For more information on how colors
+ * are packed in a long, please refer to the documentation of
+ * the {@link android.graphics.Color} class.</p>
+ *
+ * <p>Example:</p>
+ *
+ * <pre>{@code
+ *  public void setFillColor(@ColorLong long color);
+ * }</pre>
+ *
+ * @see android.graphics.Color
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({PARAMETER,METHOD,LOCAL_VARIABLE,FIELD})
+public @interface ColorLong {
+}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 64e5dfc..90fff8d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -74,6 +74,7 @@
 import android.service.autofill.IAutoFillCallback;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
+import android.text.TextAssistant;
 import android.text.TextUtils;
 import android.text.method.TextKeyListener;
 import android.transition.Scene;
@@ -783,6 +784,8 @@
 
     private VoiceInteractor mVoiceInteractor;
 
+    private TextAssistant mTextAssistant;
+
     private CharSequence mTitle;
     private int mTitleColor = 0;
 
@@ -1385,6 +1388,24 @@
     }
 
     /**
+     * Sets the default {@link TextAssistant} for {@link android.widget.TextView}s in this Activity.
+     */
+    public void setTextAssistant(TextAssistant textAssistant) {
+        mTextAssistant = textAssistant;
+    }
+
+    /**
+     * Returns the default {@link TextAssistant} for {@link android.widget.TextView}s
+     * in this Activity.
+     */
+    public TextAssistant getTextAssistant() {
+        if (mTextAssistant != null) {
+            return mTextAssistant;
+        }
+        return TextAssistant.NO_OP;
+    }
+
+    /**
      * This is called for activities that set launchMode to "singleTop" in
      * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
      * flag when calling {@link #startActivity}.  In either case, when the
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 1aa13a9..f052bf7 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1309,8 +1309,19 @@
         }
 
         @Override
-        public final void updateTimePrefs(boolean is24Hour) {
-            DateFormat.set24HourTimePref(is24Hour);
+        public final void updateTimePrefs(int timeFormatPreference) {
+            final Boolean timeFormatPreferenceBool;
+            // For convenience we are using the Intent extra values.
+            if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR) {
+                timeFormatPreferenceBool = Boolean.FALSE;
+            } else if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR) {
+                timeFormatPreferenceBool = Boolean.TRUE;
+            } else {
+                // timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT
+                // (or unknown).
+                timeFormatPreferenceBool = null;
+            }
+            DateFormat.set24HourTimePref(timeFormatPreferenceBool);
         }
 
         @Override
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 191cc49..ba6bc15 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -240,7 +240,9 @@
     /** @hide Control whether an application is allowed to run in the background. */
     public static final int OP_RUN_IN_BACKGROUND = 63;
     /** @hide */
-    public static final int _NUM_OP = 64;
+    public static final int OP_AUDIO_ACCESSIBILITY_VOLUME = 64;
+    /** @hide */
+    public static final int _NUM_OP = 65;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -452,6 +454,7 @@
             OP_TURN_SCREEN_ON,
             OP_GET_ACCOUNTS,
             OP_RUN_IN_BACKGROUND,
+            OP_AUDIO_ACCESSIBILITY_VOLUME,
     };
 
     /**
@@ -523,6 +526,7 @@
             null,
             OPSTR_GET_ACCOUNTS,
             null,
+            null, // OP_AUDIO_ACCESSIBILITY_VOLUME
     };
 
     /**
@@ -594,6 +598,7 @@
             "TURN_ON_SCREEN",
             "GET_ACCOUNTS",
             "RUN_IN_BACKGROUND",
+            "AUDIO_ACCESSIBILITY_VOLUME",
     };
 
     /**
@@ -665,6 +670,7 @@
             null, // no permission for turning the screen on
             Manifest.permission.GET_ACCOUNTS,
             null, // no permission for running in background
+            null, // no permission for changing accessibility volume
     };
 
     /**
@@ -737,6 +743,7 @@
             null, // TURN_ON_SCREEN
             null, // GET_ACCOUNTS
             null, // RUN_IN_BACKGROUND
+            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME
     };
 
     /**
@@ -808,6 +815,7 @@
             false, // TURN_ON_SCREEN
             false, // GET_ACCOUNTS
             false, // RUN_IN_BACKGROUND
+            false, // AUDIO_ACCESSIBILITY_VOLUME
     };
 
     /**
@@ -878,6 +886,7 @@
             AppOpsManager.MODE_ALLOWED,  // OP_TURN_ON_SCREEN
             AppOpsManager.MODE_ALLOWED,
             AppOpsManager.MODE_ALLOWED,  // OP_RUN_IN_BACKGROUND
+            AppOpsManager.MODE_ALLOWED,  // OP_AUDIO_ACCESSIBILITY_VOLUME
     };
 
     /**
@@ -952,6 +961,7 @@
             false,
             false,
             false,
+            false, // OP_AUDIO_ACCESSIBILITY_VOLUME
     };
 
     /**
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index e222fee..abb098f 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -534,6 +534,12 @@
         if (mSharedElementSourceNames == null) {
             mSharedElementSourceNames = new ArrayList<String>();
             mSharedElementTargetNames = new ArrayList<String>();
+        } else if (mSharedElementTargetNames.contains(name)) {
+            throw new IllegalArgumentException("A shared element with the target name '"
+                    + name + "' has already been added to the transaction.");
+        } else if (mSharedElementSourceNames.contains(transitionName)) {
+            throw new IllegalArgumentException("A shared element with the source name '"
+                    + transitionName + " has already been added to the transaction.");
         }
         mSharedElementSourceNames.add(transitionName);
         mSharedElementTargetNames.add(name);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 496c05f..b8540f8 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1918,7 +1918,6 @@
         final int displayId = mDisplay != null ? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;
         context.mResources = createResources(mActivityToken, mPackageInfo, displayId,
                 overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo());
-        context.mDisplay = mDisplay;
         return context;
     }
 
@@ -1942,14 +1941,14 @@
     public Context createDeviceProtectedStorageContext() {
         final int flags = (mFlags & ~Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE)
                 | Context.CONTEXT_DEVICE_PROTECTED_STORAGE;
-        return new ContextImplFlagContextWrapper(this, flags);
+        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags);
     }
 
     @Override
     public Context createCredentialProtectedStorageContext() {
         final int flags = (mFlags & ~Context.CONTEXT_DEVICE_PROTECTED_STORAGE)
                 | Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE;
-        return new ContextImplFlagContextWrapper(this, flags);
+        return new ContextImpl(this, mMainThread, mPackageInfo, mActivityToken, mUser, flags);
     }
 
     @Override
@@ -2103,8 +2102,8 @@
         if (container != null) {
             mBasePackageName = container.mBasePackageName;
             mOpPackageName = container.mOpPackageName;
-            mResources = container.getResources();
-            mDisplay = container.getDisplay();
+            mResources = container.mResources;
+            mDisplay = container.mDisplay;
         } else {
             mBasePackageName = packageInfo.mPackageName;
             ApplicationInfo ainfo = packageInfo.getApplicationInfo();
diff --git a/core/java/android/app/ContextImplFlagContextWrapper.java b/core/java/android/app/ContextImplFlagContextWrapper.java
deleted file mode 100644
index a83f130..0000000
--- a/core/java/android/app/ContextImplFlagContextWrapper.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.app;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-
-class ContextImplFlagContextWrapper extends ContextWrapper {
-    private final int mFlags;
-
-    public ContextImplFlagContextWrapper(Context base, int flags) {
-        super(base);
-        mFlags = flags;
-    }
-
-    @Override
-    public boolean isRestricted() {
-        return (mFlags & Context.CONTEXT_RESTRICTED) != 0;
-    }
-
-    @Override
-    public boolean isDeviceProtectedStorage() {
-        return (mFlags & Context.CONTEXT_DEVICE_PROTECTED_STORAGE) != 0;
-    }
-
-    @Override
-    public boolean isCredentialProtectedStorage() {
-        return (mFlags & Context.CONTEXT_CREDENTIAL_PROTECTED_STORAGE) != 0;
-    }
-}
diff --git a/core/java/android/app/FragmentTransition.java b/core/java/android/app/FragmentTransition.java
index 088fd08..3324448 100644
--- a/core/java/android/app/FragmentTransition.java
+++ b/core/java/android/app/FragmentTransition.java
@@ -367,9 +367,11 @@
             }
 
             if (exitingViews != null) {
-                ArrayList<View> tempExiting = new ArrayList<>();
-                tempExiting.add(nonExistentView);
-                replaceTargets(exitTransition, exitingViews, tempExiting);
+                if (exitTransition != null) {
+                    ArrayList<View> tempExiting = new ArrayList<>();
+                    tempExiting.add(nonExistentView);
+                    replaceTargets(exitTransition, exitingViews, tempExiting);
+                }
                 exitingViews.clear();
                 exitingViews.add(nonExistentView);
             }
@@ -490,9 +492,17 @@
 
         if (nameOverrides.isEmpty()) {
             sharedElementTransition = null;
+            if (outSharedElements != null) {
+                outSharedElements.clear();
+            }
+            if (inSharedElements != null) {
+                inSharedElements.clear();
+            }
         } else {
-            sharedElementsOut.addAll(outSharedElements.values());
-            sharedElementsIn.addAll(inSharedElements.values());
+            addSharedElementsWithMatchingNames(sharedElementsOut, outSharedElements,
+                    nameOverrides.keySet());
+            addSharedElementsWithMatchingNames(sharedElementsIn, inSharedElements,
+                    nameOverrides.values());
         }
 
         if (enterTransition == null && exitTransition == null && sharedElementTransition == null) {
@@ -538,6 +548,25 @@
     }
 
     /**
+     * Add Views from sharedElements into views that have the transitionName in the
+     * nameOverridesSet.
+     *
+     * @param views               Views list to add shared elements to
+     * @param sharedElements      List of shared elements
+     * @param nameOverridesSet    The transition names for all views to be copied from
+     *                            sharedElements to views.
+     */
+    private static void addSharedElementsWithMatchingNames(ArrayList<View> views,
+            ArrayMap<String, View> sharedElements, Collection<String> nameOverridesSet) {
+        for (int i = sharedElements.size() - 1; i >= 0; i--) {
+            View view = sharedElements.valueAt(i);
+            if (view != null && nameOverridesSet.contains(view.getTransitionName())) {
+                views.add(view);
+            }
+        }
+    }
+
+    /**
      * Configures the shared elements of an unoptimized fragment transaction's transition.
      * This retrieves the shared elements of the incoming fragments, and schedules capturing
      * the incoming fragment's shared elements. It also maps the views, and sets up the epicenter
diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl
index 8c9837b..6b962b9 100644
--- a/core/java/android/app/IApplicationThread.aidl
+++ b/core/java/android/app/IApplicationThread.aidl
@@ -138,7 +138,7 @@
     void scheduleTranslucentConversionComplete(IBinder token, boolean timeout);
     void setProcessState(int state);
     void scheduleInstallProvider(in ProviderInfo provider);
-    void updateTimePrefs(boolean is24Hour);
+    void updateTimePrefs(int timeFormatPreference);
     void scheduleCancelVisibleBehind(IBinder token);
     void scheduleBackgroundVisibleBehindChanged(IBinder token, boolean enabled);
     void scheduleEnterAnimationComplete(IBinder token);
@@ -152,4 +152,4 @@
             IVoiceInteractor voiceInteractor);
     void handleTrustStorageUpdate();
     void attachAgent(String path);
-}
\ No newline at end of file
+}
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 15b99c6..2a7341a 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -77,7 +77,10 @@
     void cancelNotificationFromListener(in INotificationListener token, String pkg, String tag, int id);
     void cancelNotificationsFromListener(in INotificationListener token, in String[] keys);
 
-    void snoozeNotificationFromListener(in INotificationListener token, String key, long until);
+
+    void snoozeNotificationUntilFromListener(in INotificationListener token, String key, long until);
+    void snoozeNotificationFromListener(in INotificationListener token, String key);
+    void unsnoozeNotificationFromListener(in INotificationListener token, String key);
 
     void requestBindListener(in ComponentName component);
     void requestUnbindListener(in INotificationListener token);
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index 2234a38..047f349 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
+import android.annotation.TestApi;
 import android.app.Notification.Builder;
 import android.content.ComponentName;
 import android.content.Context;
@@ -428,6 +429,7 @@
     /**
      * @hide
      */
+    @TestApi
     public ComponentName getEffectsSuppressor() {
         INotificationManager service = getService();
         try {
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index d89d2bb..b1eca5c 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.security.KeyChain;
 
 import java.lang.annotation.Retention;
@@ -306,6 +307,24 @@
             "android.app.extra.EXTRA_NETWORK_LOGS_COUNT";
 
     /**
+     * Broadcast action: notify the device owner that a user or profile has been added.
+     * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of
+     * the new user.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_USER_ADDED  = "android.app.action.USER_ADDED";
+
+    /**
+     * Broadcast action: notify the device owner that a user or profile has been removed.
+     * Carries an extra {@link Intent#EXTRA_USER} that has the {@link UserHandle} of
+     * the new user.
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_USER_REMOVED = "android.app.action.USER_REMOVED";
+
+    /**
      * A string containing the SHA-256 hash of the bugreport file.
      *
      * @see #ACTION_BUGREPORT_SHARE
@@ -690,6 +709,30 @@
             int networkLogsCount) {
     }
 
+     /**
+      * Called when a user or profile is created.
+      *
+      * <p>This callback is only applicable to device owners.
+      *
+      * @param context The running context as per {@link #onReceive}.
+      * @param intent The received intent as per {@link #onReceive}.
+      * @param newUser The {@link UserHandle} of the user that has just been added.
+      */
+     public void onUserAdded(Context context, Intent intent, UserHandle newUser) {
+     }
+
+     /**
+      * Called when a user or profile is removed.
+      *
+      * <p>This callback is only applicable to device owners.
+      *
+      * @param context The running context as per {@link #onReceive}.
+      * @param intent The received intent as per {@link #onReceive}.
+      * @param removedUser The {@link UserHandle} of the user that has just been removed.
+      */
+     public void onUserRemoved(Context context, Intent intent, UserHandle removedUser) {
+     }
+
     /**
      * Intercept standard device administrator broadcasts.  Implementations
      * should not override this method; it is better to implement the
@@ -748,6 +791,10 @@
             long batchToken = intent.getLongExtra(EXTRA_NETWORK_LOGS_TOKEN, -1);
             int networkLogsCount = intent.getIntExtra(EXTRA_NETWORK_LOGS_COUNT, 0);
             onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount);
+        } else if (ACTION_USER_ADDED.equals(action)) {
+            onUserAdded(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
+        } else if (ACTION_USER_REMOVED.equals(action)) {
+            onUserRemoved(context, intent, intent.getParcelableExtra(Intent.EXTRA_USER));
         }
     }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 0196312..e0ec1b1 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -24,6 +24,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
+import android.annotation.TestApi;
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.app.Activity;
@@ -158,6 +159,11 @@
      * managed profile and the {@link #ACTION_MANAGED_PROFILE_PROVISIONED} broadcast is sent in
      * the primary profile.
      *
+     * <p>From version {@link android.os.Build.VERSION_CODES#O}, when managed provisioning has
+     * completed, along with the above broadcast, activity intent
+     * {@link #ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the application specified in
+     * the provisioning intent.
+     *
      * <p>If provisioning fails, the managedProfile is removed so the device returns to its
      * previous state.
      *
@@ -232,6 +238,10 @@
      * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
      * device owner.
      *
+     * <p>From version {@link android.os.Build.VERSION_CODES#O}, when device owner provisioning has
+     * completed, along with the above broadcast, activity intent
+     * {@link #ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the device owner.
+     *
      * <p>If provisioning fails, the device is factory reset.
      *
      * <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
@@ -321,6 +331,10 @@
      * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} is broadcast to the
      * device owner.
      *
+     * <p>From version {@link android.os.Build.VERSION_CODES#O}, when device owner provisioning has
+     * completed, along with the above broadcast, activity intent
+     * {@link #ACTION_PROVISIONING_SUCCESSFUL} will also be sent to the device owner.
+     *
      * <p>If provisioning fails, the device is factory reset.
      *
      * <p>A result code of {@link android.app.Activity#RESULT_OK} implies that the synchronous part
@@ -733,6 +747,22 @@
         = "android.app.action.MANAGED_PROFILE_PROVISIONED";
 
     /**
+     * Activity action: This activity action is sent to indicate that provisioning of a managed
+     * profile or managed device has completed successfully. It'll be sent at the same time as
+     * {@link DeviceAdminReceiver#ACTION_PROFILE_PROVISIONING_COMPLETE} broadcast but this will be
+     * delivered faster as it's an activity intent.
+     *
+     * <p>The intent is only sent to the application on the profile that requested provisioning. In
+     * the device owner case the profile is the primary user.
+     *
+     * @see #ACTION_PROVISION_MANAGED_PROFILE
+     * @see #ACTION_PROVISION_MANAGED_DEVICE
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_PROVISIONING_SUCCESSFUL =
+            "android.app.action.PROVISIONING_SUCCESSFUL";
+
+    /**
      * A boolean extra indicating whether device encryption can be skipped as part of device owner
      * or managed profile provisioning.
      *
@@ -2372,8 +2402,6 @@
      *         {@link #KEYGUARD_DISABLE_TRUST_AGENTS}.
      *
      * @throws SecurityException if {@code admin} is not a device or profile owner.
-     *
-     * @hide
      */
     public void setRequiredStrongAuthTimeout(@NonNull ComponentName admin,
             long timeoutMs) {
@@ -2398,8 +2426,6 @@
      * @param admin The name of the admin component to check, or {@code null} to aggregate
      *         accross all participating admins.
      * @return The timeout or 0 if not configured for the provided admin.
-     *
-     * @hide
      */
     public long getRequiredStrongAuthTimeout(@Nullable ComponentName admin) {
         return getRequiredStrongAuthTimeout(admin, myUserId());
@@ -6159,6 +6185,8 @@
      * <li>{@link #setKeyguardDisabledFeatures}</li>
      * <li>{@link #getTrustAgentConfiguration}</li>
      * <li>{@link #setTrustAgentConfiguration}</li>
+     * <li>{@link #getRequiredStrongAuthTimeout}</li>
+     * <li>{@link #setRequiredStrongAuthTimeout}</li>
      * </ul>
      *
      * @return a new instance of {@link DevicePolicyManager} that acts on the parent profile.
@@ -6715,11 +6743,14 @@
     }
 
     /**
-     * Called by device owner/ profile owner in managed profile to bind the service with each other.
+     * Called by a device owner to bind to a service from a profile owner of a managed profile or
+     * vice versa. See {@link #getBindDeviceAdminTargetUsers} for a definition of which
+     * device/profile owners are allowed to bind to services of another profile/device owner.
+     * <p>
      * The service must be unexported. Note that the {@link Context} used to obtain this
      * {@link DevicePolicyManager} instance via {@link Context#getSystemService(Class)} will be used
      * to bind to the {@link android.app.Service}.
-     * STOPSHIP (b/31952368): Update the javadoc after we policy to control which packages can talk.
+     *
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
      * @param serviceIntent Identifies the service to connect to.  The Intent must specify either an
      *        explicit component name or a package name to match an
@@ -6728,11 +6759,15 @@
      *        valid {@link ServiceConnection} object; it must not be {@code null}.
      * @param flags Operation options for the binding operation. See
      *        {@link Context#bindService(Intent, ServiceConnection, int)}.
-     * @param targetUser Which user to bind to.
+     * @param targetUser Which user to bind to. Must be one of the users returned by
+     *        {@link #getBindDeviceAdminTargetUsers}, otherwise a {@link SecurityException} will
+     *        be thrown.
      * @return If you have successfully bound to the service, {@code true} is returned;
      *         {@code false} is returned if the connection is not made and you will not
      *         receive the service object.
+     *
      * @see Context#bindService(Intent, ServiceConnection, int)
+     * @see #getBindDeviceAdminTargetUsers(ComponentName)
      */
     public boolean bindDeviceAdminServiceAsUser(
             @NonNull ComponentName admin,  Intent serviceIntent, @NonNull ServiceConnection conn,
@@ -6751,14 +6786,40 @@
     }
 
     /**
+     * Returns the list of target users that the calling device or profile owner can use when
+     * calling {@link #bindDeviceAdminServiceAsUser}.
+     * <p>
+     * A device owner can bind to a service from a profile owner of a managed profile and
+     * vice versa, provided that:
+     * <ul>
+     * <li>Both belong to the same package name.
+     * <li>The managed profile is a profile of the user where the device owner is set.
+     *     See {@link UserManager#getUserProfiles()}
+     * <li>Both users are affiliated.
+     *         STOPSHIP(b/32326223) Add reference to setAffiliationIds here once public.
+     * </ul>
+     */
+    public @NonNull List<UserHandle> getBindDeviceAdminTargetUsers(@NonNull ComponentName admin) {
+        throwIfParentInstance("getBindDeviceAdminTargetUsers");
+        try {
+            return mService.getBindDeviceAdminTargetUsers(admin);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Called by the system to get the time at which the device owner last retrieved security
      * logging entries.
      *
      * @return the time at which the device owner most recently retrieved security logging entries,
      *         in milliseconds since epoch; -1 if security logging entries were never retrieved.
+     * @throws SecurityException if the caller is not the device owner, does not hold the
+     *         MANAGE_USERS permission and is not the system.
      *
      * @hide
      */
+    @TestApi
     public long getLastSecurityLogRetrievalTime() {
         try {
             return mService.getLastSecurityLogRetrievalTime();
@@ -6772,9 +6833,12 @@
      *
      * @return the time at which the device owner most recently requested a bug report, in
      *         milliseconds since epoch; -1 if a bug report was never requested.
+     * @throws SecurityException if the caller is not the device owner, does not hold the
+     *         MANAGE_USERS permission and is not the system.
      *
      * @hide
      */
+    @TestApi
     public long getLastBugReportRequestTime() {
         try {
             return mService.getLastBugReportRequestTime();
@@ -6789,9 +6853,12 @@
      *
      * @return the time at which the device owner most recently retrieved network logging events, in
      *         milliseconds since epoch; -1 if network logging events were never retrieved.
+     * @throws SecurityException if the caller is not the device owner, does not hold the
+     *         MANAGE_USERS permission and is not the system.
      *
      * @hide
      */
+    @TestApi
     public long getLastNetworkLogRetrievalTime() {
         try {
             return mService.getLastNetworkLogRetrievalTime();
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index d14e0d0..b7e0e92 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -325,6 +325,7 @@
     boolean bindDeviceAdminServiceAsUser(in ComponentName admin,
         IApplicationThread caller, IBinder token, in Intent service,
         IServiceConnection connection, int flags, int targetUserId);
+    List<UserHandle> getBindDeviceAdminTargetUsers(in ComponentName admin);
 
     long getLastSecurityLogRetrievalTime();
     long getLastBugReportRequestTime();
diff --git a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
index 5d27662..94d03e5 100644
--- a/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
+++ b/core/java/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -235,6 +235,7 @@
         // >=0: registered and advertising started
         private int mAdvertiserId;
         private boolean mIsAdvertising = false;
+        private int registrationError = AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR;
 
         public AdvertiseCallbackWrapper(AdvertiseCallback advertiseCallback,
                 AdvertiseData advertiseData, AdvertiseData scanResponse,
@@ -262,12 +263,11 @@
                     mLeAdvertisers.put(mAdvertiseCallback, this);
                 } else if (mAdvertiserId < 0) {
 
-                    // Registration timeout, reset mClientIf to -1 so no subsequent operations can
+                    // Registration timeout, reset mClientIf to -2 so no subsequent operations can
                     // proceed.
-                    if (mAdvertiserId == 0) mAdvertiserId = -2;
+                    if (mAdvertiserId == -1) mAdvertiserId = -2;
                     // Post internal error if registration failed.
-                    postStartFailure(mAdvertiseCallback,
-                            AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
+                    postStartFailure(mAdvertiseCallback, registrationError);
                 } else {
                     // Unregister application if it's already registered but advertise failed.
                     try {
@@ -318,6 +318,8 @@
                     } catch (RemoteException e) {
                         Log.e(TAG, "failed to start advertising", e);
                     }
+                } else if (status == AdvertiseCallback.ADVERTISE_FAILED_TOO_MANY_ADVERTISERS) {
+                    registrationError = status;
                 }
                 // Registration failed.
                 mAdvertiserId = -2;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c87de9a..50589fe 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -4186,13 +4186,21 @@
             = "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
 
     /**
-     * Optional boolean extra for {@link #ACTION_TIME_CHANGED} that indicates the
-     * user has set their time format preferences to the 24 hour format.
+     * Optional int extra for {@link #ACTION_TIME_CHANGED} that indicates the
+     * user has set their time format preference. See {@link #EXTRA_TIME_PREF_VALUE_USE_12_HOUR},
+     * {@link #EXTRA_TIME_PREF_VALUE_USE_24_HOUR} and
+     * {@link #EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT}. The value must not be negative.
      *
      * @hide for internal use only.
      */
     public static final String EXTRA_TIME_PREF_24_HOUR_FORMAT =
             "android.intent.extra.TIME_PREF_24_HOUR_FORMAT";
+    /** @hide */
+    public static final int EXTRA_TIME_PREF_VALUE_USE_12_HOUR = 0;
+    /** @hide */
+    public static final int EXTRA_TIME_PREF_VALUE_USE_24_HOUR = 1;
+    /** @hide */
+    public static final int EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT = 2;
 
     /** {@hide} */
     public static final String EXTRA_REASON = "android.intent.extra.REASON";
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index cdf7013..aa109de 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -26,6 +26,7 @@
 import android.os.CancellationSignal.OnCancelListener;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -757,20 +758,22 @@
                         new IFingerprintServiceLockoutResetCallback.Stub() {
 
                     @Override
-                    public void onLockoutReset(long deviceId) throws RemoteException {
-                        final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
-                                PowerManager.PARTIAL_WAKE_LOCK, "lockoutResetCallback");
-                        wakeLock.acquire();
-                        mHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
+                    public void onLockoutReset(long deviceId, IRemoteCallback serverCallback)
+                            throws RemoteException {
+                        try {
+                            final PowerManager.WakeLock wakeLock = powerManager.newWakeLock(
+                                    PowerManager.PARTIAL_WAKE_LOCK, "lockoutResetCallback");
+                            wakeLock.acquire();
+                            mHandler.post(() -> {
                                 try {
                                     callback.onLockoutReset();
                                 } finally {
                                     wakeLock.release();
                                 }
-                            }
-                        });
+                            });
+                        } finally {
+                            serverCallback.sendResult(null /* data */);
+                        }
                     }
                 });
             } catch (RemoteException e) {
diff --git a/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl b/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl
index e027a2b3..971e14c 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintServiceLockoutResetCallback.aidl
@@ -17,14 +17,17 @@
 
 import android.hardware.fingerprint.Fingerprint;
 import android.os.Bundle;
+import android.os.IRemoteCallback;
 import android.os.UserHandle;
 
 /**
  * Callback when lockout period expired and clients are allowed to authenticate again.
  * @hide
  */
-interface IFingerprintServiceLockoutResetCallback {
+oneway interface IFingerprintServiceLockoutResetCallback {
 
-    /** Method is synchronous so wakelock is held when this is called from a WAKEUP alarm. */
-    void onLockoutReset(long deviceId);
+    /**
+     * A wakelock will be held until the reciever calls back into {@param callback}
+     */
+    void onLockoutReset(long deviceId, IRemoteCallback callback);
 }
diff --git a/core/java/android/net/RoughtimeClient.java b/core/java/android/net/RoughtimeClient.java
deleted file mode 100644
index cf4d8a2..0000000
--- a/core/java/android/net/RoughtimeClient.java
+++ /dev/null
@@ -1,499 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.security.MessageDigest;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-
-/**
- * {@hide}
- *
- * Simple Roughtime client class for retrieving network time.
- */
-public class RoughtimeClient
-{
-    private static final String TAG = "RoughtimeClient";
-    private static final boolean ENABLE_DEBUG = true;
-
-    private static final int ROUGHTIME_PORT = 5333;
-
-    private static final int MIN_REQUEST_SIZE = 1024;
-
-    private static final int NONCE_SIZE = 64;
-
-    private static final int MAX_DATAGRAM_SIZE = 65507;
-
-    private final SecureRandom random = new SecureRandom();
-
-    /**
-     * Tag values. Exposed for use in tests only.
-     */
-    protected static enum Tag {
-        /**
-         * Nonce used to initiate a transaction.
-         */
-        NONC(0x434e4f4e),
-
-        /**
-         * Signed portion of a response.
-         **/
-        SREP(0x50455253),
-
-        /**
-         * Pad data. Always the largest tag lexicographically.
-         */
-        PAD(0xff444150),
-
-        /**
-         * A signature for a neighboring SREP.
-         */
-        SIG(0x474953),
-
-        /**
-         * Server certificate.
-         */
-        CERT(0x54524543),
-
-        /**
-         * Position in the Merkle tree.
-         */
-        INDX(0x58444e49),
-
-        /**
-         * Upward path in the Merkle tree.
-         */
-        PATH(0x48544150),
-
-        /**
-         * Midpoint of the time interval in the response.
-         */
-        MIDP(0x5044494d),
-
-        /**
-         * Radius of the time interval in the response.
-         */
-        RADI(0x49444152),
-
-        /**
-         * Root of the Merkle tree.
-         */
-        ROOT(0x544f4f52),
-
-        /**
-         * Delegation from the long term key to an online key.
-         */
-        DELE(0x454c4544),
-
-        /**
-         * Online public key.
-         */
-        PUBK(0x4b425550),
-
-        /**
-         * Earliest midpoint time the given PUBK can authenticate.
-         */
-        MINT(0x544e494d),
-
-        /**
-         * Latest midpoint time the given PUBK can authenticate.
-         */
-        MAXT(0x5458414d);
-
-        private final int value;
-
-        Tag(int value) {
-            this.value = value;
-        }
-
-        private int value() {
-            return value;
-        }
-    }
-
-    /**
-     * A result retrieved from a roughtime server.
-     */
-    private static class Result {
-        public long midpoint;
-        public int radius;
-        public long collectionTime;
-    }
-
-    /**
-     * A Roughtime protocol message. Functionally a serializable map from Tags
-     * to byte arrays.
-     */
-    protected static class Message {
-        private HashMap<Integer,byte[]> items = new HashMap<Integer,byte[]>();
-        public int padSize = 0;
-
-        public Message() {}
-
-        /**
-         * Set the given data for the given tag.
-         */
-        public void put(Tag tag, byte[] data) {
-            put(tag.value(), data);
-        }
-
-        private void put(int tag, byte[] data) {
-            items.put(tag, data);
-        }
-
-        /**
-         * Get the data associated with the given tag.
-         */
-        public byte[] get(Tag tag) {
-            return items.get(tag.value());
-        }
-
-        /**
-         * Get the data associated with the given tag and decode it as a 64-bit
-         * integer.
-         */
-        public long getLong(Tag tag) {
-            ByteBuffer b = ByteBuffer.wrap(get(tag));
-            return b.getLong();
-        }
-
-        /**
-         * Get the data associated with the given tag and decode it as a 32-bit
-         * integer.
-         */
-        public int getInt(Tag tag) {
-            ByteBuffer b = ByteBuffer.wrap(get(tag));
-            return b.getInt();
-        }
-
-        /**
-         * Encode the given long value as a 64-bit little-endian value and
-         * associate it with the given tag.
-         */
-        public void putLong(Tag tag, long l) {
-            ByteBuffer b = ByteBuffer.allocate(8);
-            b.putLong(l);
-            put(tag, b.array());
-        }
-
-        /**
-         * Encode the given int value as a 32-bit little-endian value and
-         * associate it with the given tag.
-         */
-        public void putInt(Tag tag, int l) {
-            ByteBuffer b = ByteBuffer.allocate(4);
-            b.putInt(l);
-            put(tag, b.array());
-        }
-
-        /**
-         * Get a packed representation of this message suitable for the wire.
-         */
-        public byte[] serialize() {
-            if (items.size() == 0) {
-                if (padSize > 4)
-                    return new byte[padSize];
-                else
-                    return new byte[4];
-            }
-
-            int size = 0;
-
-            ArrayList<Integer> offsets = new ArrayList<Integer>();
-            ArrayList<Integer> tagList = new ArrayList<Integer>(items.keySet());
-            Collections.sort(tagList);
-
-            boolean first = true;
-            for (int tag : tagList) {
-                if (! first) {
-                    offsets.add(size);
-                }
-
-                first = false;
-                size += items.get(tag).length;
-            }
-
-            ByteBuffer dataBuf = ByteBuffer.allocate(size);
-            dataBuf.order(ByteOrder.LITTLE_ENDIAN);
-
-            int valueDataSize = size;
-            size += 4 + offsets.size() * 4 + tagList.size() * 4;
-
-            int tagCount = items.size();
-
-            if (size < padSize) {
-                offsets.add(valueDataSize);
-                tagList.add(Tag.PAD.value());
-
-                if (size + 8 > padSize) {
-                    size = size + 8;
-                } else {
-                    size = padSize;
-                }
-
-                tagCount += 1;
-            }
-
-            ByteBuffer buf = ByteBuffer.allocate(size);
-            buf.order(ByteOrder.LITTLE_ENDIAN);
-            buf.putInt(tagCount);
-
-            for (int offset : offsets) {
-                buf.putInt(offset);
-            }
-
-            for (int tag : tagList) {
-                buf.putInt(tag);
-
-                if (tag != Tag.PAD.value()) {
-                    dataBuf.put(items.get(tag));
-                }
-            }
-
-            buf.put(dataBuf.array());
-
-            return buf.array();
-        }
-
-        /**
-         * Given a byte stream from the wire, unpack it into a Message object.
-         */
-        public static Message deserialize(byte[] data) {
-            ByteBuffer buf = ByteBuffer.wrap(data);
-            buf.order(ByteOrder.LITTLE_ENDIAN);
-
-            Message msg = new Message();
-
-            int count = buf.getInt();
-
-            if (count == 0) {
-                return msg;
-            }
-
-            ArrayList<Integer> offsets = new ArrayList<Integer>();
-            offsets.add(0);
-
-            for (int i = 1; i < count; i++) {
-                offsets.add(buf.getInt());
-            }
-
-            ArrayList<Integer> tags = new ArrayList<Integer>();
-            for (int i = 0; i < count; i++) {
-                int tag = buf.getInt();
-                tags.add(tag);
-            }
-
-            offsets.add(buf.remaining());
-
-            for (int i = 0; i < count; i++) {
-                int tag = tags.get(i);
-                int start = offsets.get(i);
-                int end = offsets.get(i+1);
-                byte[] content = new byte[end - start];
-
-                buf.get(content);
-                if (tag != Tag.PAD.value()) {
-                    msg.put(tag, content);
-                }
-            }
-
-            return msg;
-        }
-
-        /**
-         * Send this message over the given socket to the given address and port.
-         */
-        public void send(DatagramSocket socket, InetAddress address, int port)
-                throws IOException {
-            byte[] buffer = serialize();
-            DatagramPacket message = new DatagramPacket(buffer, buffer.length,
-                    address, port);
-            socket.send(message);
-        }
-
-        /**
-         * Receive a Message object from the given socket.
-         */
-        public static Message receive(DatagramSocket socket)
-                throws IOException {
-            byte[] buffer = new byte[MAX_DATAGRAM_SIZE];
-            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
-
-            socket.receive(packet);
-
-            return deserialize(Arrays.copyOf(buffer, packet.getLength()));
-        }
-    }
-
-    private MessageDigest messageDigest = null;
-
-    private final ArrayList<Result> results = new ArrayList<Result>();
-    private long lastRequest = 0;
-
-    private Message createRequestMessage() {
-        byte[] nonce = new byte[NONCE_SIZE];
-        random.nextBytes(nonce); // TODO: Chain nonces
-
-        assert nonce.length == NONCE_SIZE :
-            "Nonce must be " + NONCE_SIZE + " bytes.";
-
-        Message msg = new Message();
-
-        msg.put(Tag.NONC, nonce);
-        msg.padSize = MIN_REQUEST_SIZE;
-
-        return msg;
-    }
-
-    /**
-     * Contact the Roughtime server at the given address and port and collect a
-     * result time to add to our collection.
-     *
-     * @param host host name of the server.
-     * @param timeout network timeout in milliseconds.
-     * @return true if the transaction was successful.
-     */
-    public boolean requestTime(String host, int timeout) {
-        InetAddress address = null;
-        try {
-            address = InetAddress.getByName(host);
-        } catch (Exception e) {
-            if (ENABLE_DEBUG) {
-                Log.d(TAG, "request time failed", e);
-            }
-
-            return false;
-        }
-        return requestTime(address, ROUGHTIME_PORT, timeout);
-    }
-
-    /**
-     * Contact the Roughtime server at the given address and port and collect a
-     * result time to add to our collection.
-     *
-     * @param address address for the server.
-     * @param port port to talk to the server on.
-     * @param timeout network timeout in milliseconds.
-     * @return true if the transaction was successful.
-     */
-    public boolean requestTime(InetAddress address, int port, int timeout) {
-
-        final long rightNow = SystemClock.elapsedRealtime();
-
-        if ((rightNow - lastRequest) > timeout) {
-            results.clear();
-        }
-
-        lastRequest = rightNow;
-
-        DatagramSocket socket = null;
-        try {
-            if (messageDigest == null) {
-                messageDigest = MessageDigest.getInstance("SHA-512");
-            }
-
-            socket = new DatagramSocket();
-            socket.setSoTimeout(timeout);
-            final long startTime = SystemClock.elapsedRealtime();
-            Message request = createRequestMessage();
-            request.send(socket, address, port);
-            final long endTime = SystemClock.elapsedRealtime();
-            Message response = Message.receive(socket);
-            byte[] signedData = response.get(Tag.SREP);
-            Message signedResponse = Message.deserialize(signedData);
-
-            final Result result = new Result();
-            result.midpoint = signedResponse.getLong(Tag.MIDP);
-            result.radius = signedResponse.getInt(Tag.RADI);
-            result.collectionTime = (startTime + endTime) / 2;
-
-            final byte[] root = signedResponse.get(Tag.ROOT);
-            final byte[] path = response.get(Tag.PATH);
-            final byte[] nonce = request.get(Tag.NONC);
-            final int index = response.getInt(Tag.INDX);
-
-            if (! verifyNonce(root, path, nonce, index)) {
-                Log.w(TAG, "failed to authenticate roughtime response.");
-                return false;
-            }
-
-            results.add(result);
-        } catch (Exception e) {
-            if (ENABLE_DEBUG) {
-                Log.d(TAG, "request time failed", e);
-            }
-
-            return false;
-        } finally {
-            if (socket != null) {
-                socket.close();
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Verify that a reply message corresponds to the nonce sent in the request.
-     *
-     * @param root  Root of the Merkle tree used to sign the nonce. Received in
-     *              the ROOT tag of the reply.
-     * @param path  Sibling hashes along the path to the root of the Merkle tree.
-     *              Received in the PATH tag of the reply.
-     * @param nonce The nonce we sent in the request.
-     * @param index Bitfield indicating whether chunks of the path are left or
-     *              right children.
-     * @return true if the verification is successful.
-     */
-    private boolean verifyNonce(byte[] root, byte[] path, byte[] nonce,
-            int index) {
-        messageDigest.update(new byte[]{ 0 });
-        byte[] hash = messageDigest.digest(nonce);
-        int pos = 0;
-        byte[] one = new byte[]{ 1 };
-
-        while (pos < path.length) {
-            messageDigest.update(one);
-
-            if ((index&1) != 0) {
-                messageDigest.update(path, pos, 64);
-                hash = messageDigest.digest(hash);
-            } else {
-                messageDigest.update(hash);
-                messageDigest.update(path, pos, 64);
-                hash = messageDigest.digest();
-            }
-
-            pos += 64;
-            index >>>= 1;
-        }
-
-        return Arrays.equals(root, hash);
-    }
-}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 44ae6ee..760df45 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -316,6 +316,16 @@
         stringToFile(file.getAbsolutePath(), string);
     }
 
+    /*
+     * Writes the bytes given in {@code content} to the file whose absolute path
+     * is {@code filename}.
+     */
+    public static void bytesToFile(String filename, byte[] content) throws IOException {
+        try (FileOutputStream fos = new FileOutputStream(filename)) {
+            fos.write(content);
+        }
+    }
+
     /**
      * Writes string to file. Basically same as "echo -n $string > $filename"
      *
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index d31036c..6a751e8 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -77,6 +77,7 @@
     private static native boolean native_get_boolean(String key, boolean def);
     private static native void native_set(String key, String def);
     private static native void native_add_change_callback();
+    private static native void native_report_sysprop_change();
 
     /**
      * Get the value for the given key.
@@ -195,4 +196,11 @@
             }
         }
     }
+
+    /*
+     * Notifies listeners that a system property has changed
+     */
+    public static void reportSyspropChanged() {
+        native_report_sysprop_change();
+    }
 }
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index afbc09b..0946906 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -369,6 +369,22 @@
             "android.settings.WIFI_IP_SETTINGS";
 
     /**
+     * Activity Action: Show settings to allow configuration of Wi-Fi saved networks.
+     * <p>
+     * In some cases, a matching Activity may not exist, so ensure you
+     * safeguard against this.
+     * <p>
+     * Input: Nothing.
+     * <p>
+     * Output: Nothing.
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_WIFI_SAVED_NETWORK_SETTINGS =
+            "android.settings.WIFI_SAVED_NETWORK_SETTINGS";
+
+    /**
      * Activity Action: Show settings to allow configuration of Bluetooth.
      * <p>
      * In some cases, a matching Activity may not exist, so ensure you
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 45011eb..02ab30a 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -534,7 +534,46 @@
     public final void snoozeNotification(String key, long snoozeUntil) {
         if (!isBound()) return;
         try {
-            getNotificationInterface().snoozeNotificationFromListener(mWrapper, key, snoozeUntil);
+            getNotificationInterface().snoozeNotificationUntilFromListener(
+                    mWrapper, key, snoozeUntil);
+        } catch (android.os.RemoteException ex) {
+            Log.v(TAG, "Unable to contact notification manager", ex);
+        }
+    }
+
+    /**
+     * Inform the notification manager about snoozing a specific notification.
+     * <p>
+     * Use this to snooze a notification for an indeterminate time.  Upon being informed, the
+     * notification manager will actually remove the notification and you will get an
+     * {@link #onNotificationRemoved(StatusBarNotification)} callback. When the
+     * snoozing period expires, you will get a
+     * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
+     * notification. Use {@link #unsnoozeNotification(String)} to restore the notification.
+     * @param key The key of the notification to snooze
+     */
+    public final void snoozeNotification(String key) {
+        if (!isBound()) return;
+        try {
+            getNotificationInterface().snoozeNotificationFromListener(mWrapper, key);
+        } catch (android.os.RemoteException ex) {
+            Log.v(TAG, "Unable to contact notification manager", ex);
+        }
+    }
+
+    /**
+     * Inform the notification manager about un-snoozing a specific notification.
+     * <p>
+     * This should only be used for notifications snoozed by this listener using
+     * {@link #snoozeNotification(String)}. Once un-snoozed, you will get a
+     * {@link #onNotificationPosted(StatusBarNotification, RankingMap)} callback for the
+     * notification.
+     * @param key The key of the notification to snooze
+     */
+    public final void unsnoozeNotification(String key) {
+        if (!isBound()) return;
+        try {
+            getNotificationInterface().unsnoozeNotificationFromListener(mWrapper, key);
         } catch (android.os.RemoteException ex) {
             Log.v(TAG, "Unable to contact notification manager", ex);
         }
diff --git a/core/java/android/text/TextAssistant.java b/core/java/android/text/TextAssistant.java
new file mode 100644
index 0000000..b044981
--- /dev/null
+++ b/core/java/android/text/TextAssistant.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+/**
+ * Interface for providing text assistant features.
+ */
+public interface TextAssistant {
+
+    /**
+     * NO_OP TextAssistant. This will act as the default TextAssistant until we implement a
+     * TextClassificationManager.
+     * @hide
+     */
+    TextAssistant NO_OP = new TextAssistant() {
+
+        private final TextSelection mTextSelection = new TextSelection();
+
+        @Override
+        public TextSelection suggestSelection(
+                CharSequence text, int selectionStartIndex, int selectionEndIndex) {
+            mTextSelection.mStartIndex = selectionStartIndex;
+            mTextSelection.mEndIndex = selectionEndIndex;
+            return mTextSelection;
+        }
+
+        @Override
+        public void addLinks(Spannable text, int linkMask) {}
+    };
+
+    /**
+     * Returns suggested text selection indices, recognized types and their associated confidence
+     * scores. The selections are ordered from highest to lowest scoring.
+     */
+    TextSelection suggestSelection(
+            CharSequence text, int selectionStartIndex, int selectionEndIndex);
+
+    /**
+     * Adds assistance clickable spans to the provided text.
+     */
+    void addLinks(Spannable text, int linkMask);
+}
diff --git a/core/java/android/text/TextClassification.java b/core/java/android/text/TextClassification.java
new file mode 100644
index 0000000..bb226da
--- /dev/null
+++ b/core/java/android/text/TextClassification.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Information about entities that a specific piece of text is classified as.
+ */
+public class TextClassification {
+
+    /** @hide */
+    public static final TextClassification NO_OP = new TextClassification();
+
+    private Map<String, Float> mTypeConfidence = Collections.unmodifiableMap(Collections.EMPTY_MAP);
+
+    /**
+     * Returns a map of text classification types to their respective confidence scores.
+     * The scores range from 0 (low confidence) to 1 (high confidence). The items are ordered from
+     * high scoring items to low scoring items.
+     */
+    public Map<String, Float> getTypeConfidence() {
+        return mTypeConfidence;
+    }
+}
diff --git a/core/java/android/text/TextSelection.java b/core/java/android/text/TextSelection.java
new file mode 100644
index 0000000..9400458
--- /dev/null
+++ b/core/java/android/text/TextSelection.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+/**
+ * Text selection information.
+ */
+public class TextSelection {
+
+    /** @hide */
+    int mStartIndex;
+    /** @hide */
+    int mEndIndex;
+
+    private TextClassification mTextClassification = TextClassification.NO_OP;
+
+    /**
+     * Returns the start index of the text selection.
+     */
+    public int getSelectionStartIndex() {
+        return mStartIndex;
+    }
+
+    /**
+     * Returns the end index of the text selection.
+     */
+    public int getSelectionEndIndex() {
+        return mEndIndex;
+    }
+
+    /**
+     * Returns information about what the text selection is classified as.
+     */
+    public TextClassification getTextClassification() {
+        return mTextClassification;
+    }
+}
diff --git a/core/java/android/util/BootTimingsTraceLog.java b/core/java/android/util/BootTimingsTraceLog.java
new file mode 100644
index 0000000..2e4319c
--- /dev/null
+++ b/core/java/android/util/BootTimingsTraceLog.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.util;
+
+import android.os.Build;
+import android.os.SystemClock;
+import android.os.Trace;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * Helper class for reporting boot timing metrics.
+ * @hide
+ */
+public class BootTimingsTraceLog {
+    // Debug boot time for every step if it's non-user build.
+    private static final boolean DEBUG_BOOT_TIME = !"user".equals(Build.TYPE);
+    private final Deque<Pair<String, Long>> mStartTimes
+            = DEBUG_BOOT_TIME ? new ArrayDeque<>() : null;
+    private final String mTag;
+    private long mTraceTag;
+
+    public BootTimingsTraceLog(String tag, long traceTag) {
+        mTag = tag;
+        mTraceTag = traceTag;
+    }
+
+    public void traceBegin(String name) {
+        Trace.traceBegin(mTraceTag, name);
+        if (DEBUG_BOOT_TIME) {
+            mStartTimes.push(Pair.create(name, SystemClock.elapsedRealtime()));
+        }
+    }
+
+    public void traceEnd() {
+        Trace.traceEnd(mTraceTag);
+        if (!DEBUG_BOOT_TIME) {
+            return;
+        }
+        if (mStartTimes.peek() == null) {
+            Slog.w(mTag, "traceEnd called more times than traceBegin");
+            return;
+        }
+        Pair<String, Long> event = mStartTimes.pop();
+        // Log the duration so it can be parsed by external tools for performance reporting
+        Slog.d(mTag, event.first + " took to complete: "
+                + (SystemClock.elapsedRealtime() - event.second) + "ms");
+    }
+}
diff --git a/core/java/android/util/Half.java b/core/java/android/util/Half.java
index 1abc10d..e4f8dd1 100644
--- a/core/java/android/util/Half.java
+++ b/core/java/android/util/Half.java
@@ -543,9 +543,9 @@
 
     /**
      * <p>Converts the specified half-precision float value into a
-     * single-precision float value with the following special cases:</p>
+     * single-precision float value. The following special cases are handled:</p>
      * <ul>
-     * <li>If the input is {@link #NaN}, the returned* value is {@link Float#NaN}</li>
+     * <li>If the input is {@link #NaN}, the returned value is {@link Float#NaN}</li>
      * <li>If the input is {@link #POSITIVE_INFINITY} or
      * {@link #NEGATIVE_INFINITY}, the returned value is respectively
      * {@link Float#POSITIVE_INFINITY} or {@link Float#NEGATIVE_INFINITY}</li>
@@ -587,7 +587,7 @@
 
     /**
      * <p>Converts the specified single-precision float value into a
-     * half-precision float value with the following special cases:</p>
+     * half-precision float value. The following special cases are handled:</p>
      * <ul>
      * <li>If the input is NaN (see {@link Float#isNaN(float)}), the returned
      * value is {@link #NaN}</li>
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index c46acae..0da710a 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -28,6 +28,8 @@
 import android.widget.ImageView;
 import android.widget.RemoteViews;
 
+import com.android.internal.widget.CachingIconView;
+
 import java.util.ArrayList;
 
 /**
@@ -45,7 +47,7 @@
     private OnClickListener mExpandClickListener;
     private HeaderTouchListener mTouchListener = new HeaderTouchListener();
     private ImageView mExpandButton;
-    private View mIcon;
+    private CachingIconView mIcon;
     private View mProfileBadge;
     private View mInfo;
     private int mIconColor;
@@ -123,7 +125,7 @@
         if (mExpandButton != null) {
             mExpandButton.setAccessibilityDelegate(mExpandDelegate);
         }
-        mIcon = findViewById(com.android.internal.R.id.icon);
+        mIcon = (CachingIconView) findViewById(com.android.internal.R.id.icon);
         mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
     }
 
@@ -311,6 +313,10 @@
         return mProfileBadge;
     }
 
+    public CachingIconView getIcon() {
+        return mIcon;
+    }
+
     public class HeaderTouchListener implements View.OnTouchListener {
 
         private final ArrayList<Rect> mTouchRects = new ArrayList<>();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 02a8521..84d7548 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -37,6 +37,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.Size;
+import android.annotation.TestApi;
 import android.annotation.UiThread;
 import android.content.ClipData;
 import android.content.Context;
@@ -111,6 +112,7 @@
 
 import com.android.internal.R;
 import com.android.internal.util.Predicate;
+import com.android.internal.view.TooltipPopup;
 import com.android.internal.view.menu.MenuBuilder;
 import com.android.internal.widget.ScrollBarUtils;
 
@@ -1196,6 +1198,12 @@
 
     private static Paint sDebugPaint;
 
+    /**
+     * <p>Indicates this view can display a tooltip on hover or long press.</p>
+     * {@hide}
+     */
+    static final int TOOLTIP = 0x40000000;
+
     /** @hide */
     @IntDef(flag = true,
             value = {
@@ -3619,6 +3627,39 @@
 
     ListenerInfo mListenerInfo;
 
+    private static class TooltipInfo {
+        /**
+         * Text to be displayed in a tooltip popup.
+         */
+        @Nullable
+        CharSequence mTooltip;
+
+        /**
+         * View-relative position of the tooltip anchor point.
+         */
+        int mAnchorX;
+        int mAnchorY;
+
+        /**
+         * The tooltip popup.
+         */
+        @Nullable
+        TooltipPopup mTooltipPopup;
+
+        /**
+         * Set to true if the tooltip was shown as a result of a long click.
+         */
+        boolean mTooltipFromLongClick;
+
+        /**
+         * Keep these Runnables so that they can be used to reschedule.
+         */
+        Runnable mShowTooltipRunnable;
+        Runnable mHideTooltipRunnable;
+    }
+
+    TooltipInfo mTooltipInfo;
+
     // Temporary values used to hold (x,y) coordinates when delegating from the
     // two-arg performLongClick() method to the legacy no-arg version.
     private float mLongClickX = Float.NaN;
@@ -4576,6 +4617,9 @@
                     }
                     break;
 
+                case R.styleable.View_tooltip:
+                    setTooltip(a.getText(attr));
+                    break;
             }
         }
 
@@ -5712,6 +5756,11 @@
             final boolean isAnchored = !Float.isNaN(x) && !Float.isNaN(y);
             handled = isAnchored ? showContextMenu(x, y) : showContextMenu();
         }
+        if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+            if (!handled) {
+                handled = showLongClickTooltip((int) x, (int) y);
+            }
+        }
         if (handled) {
             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
         }
@@ -10603,17 +10652,21 @@
                 return true;
             }
 
-            // Long clickable items don't necessarily have to be clickable.
-            if (((mViewFlags & CLICKABLE) == CLICKABLE
-                    || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
-                    && (event.getRepeatCount() == 0)) {
-                // For the purposes of menu anchoring and drawable hotspots,
-                // key events are considered to be at the center of the view.
-                final float x = getWidth() / 2f;
-                final float y = getHeight() / 2f;
-                setPressed(true, x, y);
-                checkForLongClick(0, x, y);
-                return true;
+            if (event.getRepeatCount() == 0) {
+                // Long clickable items don't necessarily have to be clickable.
+                final boolean clickable = (mViewFlags & CLICKABLE) == CLICKABLE
+                        || (mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE;
+                if (clickable || (mViewFlags & TOOLTIP) == TOOLTIP) {
+                    // For the purposes of menu anchoring and drawable hotspots,
+                    // key events are considered to be at the center of the view.
+                    final float x = getWidth() / 2f;
+                    final float y = getHeight() / 2f;
+                    if (clickable) {
+                        setPressed(true, x, y);
+                    }
+                    checkForLongClick(0, x, y);
+                    return true;
+                }
             }
         }
 
@@ -11160,15 +11213,17 @@
         final int viewFlags = mViewFlags;
         final int action = event.getAction();
 
+        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
+                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
+                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
+
         if ((viewFlags & ENABLED_MASK) == DISABLED) {
             if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                 setPressed(false);
             }
             // A disabled view that is clickable still consumes the touch
             // events, it just doesn't respond to them.
-            return (((viewFlags & CLICKABLE) == CLICKABLE
-                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
-                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
+            return clickable;
         }
         if (mTouchDelegate != null) {
             if (mTouchDelegate.onTouchEvent(event)) {
@@ -11176,11 +11231,20 @@
             }
         }
 
-        if (((viewFlags & CLICKABLE) == CLICKABLE ||
-                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
-                (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
+        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
             switch (action) {
                 case MotionEvent.ACTION_UP:
+                    if ((viewFlags & TOOLTIP) == TOOLTIP) {
+                        handleTooltipUp();
+                    }
+                    if (!clickable) {
+                        removeTapCallback();
+                        removeLongPressCallback();
+                        mInContextButtonPress = false;
+                        mHasPerformedLongPress = false;
+                        mIgnoreNextUpEvent = false;
+                        break;
+                    }
                     boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                     if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                         // take focus if we don't have it already and we should in
@@ -11196,7 +11260,7 @@
                             // state now (before scheduling the click) to ensure
                             // the user sees it.
                             setPressed(true, x, y);
-                       }
+                        }
 
                         if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                             // This is a tap, so remove the longpress check
@@ -11236,6 +11300,11 @@
                 case MotionEvent.ACTION_DOWN:
                     mHasPerformedLongPress = false;
 
+                    if (!clickable) {
+                        checkForLongClick(0, x, y);
+                        break;
+                    }
+
                     if (performButtonActionOnTouchDown(event)) {
                         break;
                     }
@@ -11261,7 +11330,9 @@
                     break;
 
                 case MotionEvent.ACTION_CANCEL:
-                    setPressed(false);
+                    if (clickable) {
+                        setPressed(false);
+                    }
                     removeTapCallback();
                     removeLongPressCallback();
                     mInContextButtonPress = false;
@@ -11270,16 +11341,17 @@
                     break;
 
                 case MotionEvent.ACTION_MOVE:
-                    drawableHotspotChanged(x, y);
+                    if (clickable) {
+                        drawableHotspotChanged(x, y);
+                    }
 
                     // Be lenient about moving outside of buttons
                     if (!pointInView(x, y, mTouchSlop)) {
                         // Outside button
+                        // Remove any future long press/tap checks
                         removeTapCallback();
+                        removeLongPressCallback();
                         if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
-                            // Remove any future long press/tap checks
-                            removeLongPressCallback();
-
                             setPressed(false);
                         }
                     }
@@ -11311,7 +11383,7 @@
      */
     private void removeLongPressCallback() {
         if (mPendingCheckForLongPress != null) {
-          removeCallbacks(mPendingCheckForLongPress);
+            removeCallbacks(mPendingCheckForLongPress);
         }
     }
 
@@ -15379,6 +15451,10 @@
 
         cleanupDraw();
         mCurrentAnimation = null;
+
+        if ((mViewFlags & TOOLTIP) == TOOLTIP) {
+            hideTooltip();
+        }
     }
 
     private void cleanupDraw() {
@@ -21031,7 +21107,7 @@
     }
 
     private void checkForLongClick(int delayOffset, float x, float y) {
-        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
+        if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
             mHasPerformedLongPress = false;
 
             if (mPendingCheckForLongPress == null) {
@@ -21039,6 +21115,7 @@
             }
             mPendingCheckForLongPress.setAnchor(x, y);
             mPendingCheckForLongPress.rememberWindowAttachCount();
+            mPendingCheckForLongPress.rememberPressedState();
             postDelayed(mPendingCheckForLongPress,
                     ViewConfiguration.getLongPressTimeout() - delayOffset);
         }
@@ -22439,10 +22516,11 @@
         private int mOriginalWindowAttachCount;
         private float mX;
         private float mY;
+        private boolean mOriginalPressedState;
 
         @Override
         public void run() {
-            if (isPressed() && (mParent != null)
+            if ((mOriginalPressedState == isPressed()) && (mParent != null)
                     && mOriginalWindowAttachCount == mWindowAttachCount) {
                 if (performLongClick(mX, mY)) {
                     mHasPerformedLongPress = true;
@@ -22458,6 +22536,10 @@
         public void rememberWindowAttachCount() {
             mOriginalWindowAttachCount = mWindowAttachCount;
         }
+
+        public void rememberPressedState() {
+            mOriginalPressedState = isPressed();
+        }
     }
 
     private final class CheckForTap implements Runnable {
@@ -23246,6 +23328,12 @@
          */
         public Surface mDragSurface;
 
+
+        /**
+         * The view that currently has a tooltip displayed.
+         */
+        View mTooltipHost;
+
         /**
          * Creates a new set of attachment information with the specified
          * events handler and thread.
@@ -23982,4 +24070,167 @@
         return mAttachInfo.mTmpLocation[0] == insets.getStableInsetLeft()
                 && mAttachInfo.mTmpLocation[1] == insets.getStableInsetTop();
     }
+
+    /**
+     * Sets the tooltip text which will be displayed in a small popup next to the view.
+     * <p>
+     * The tooltip will be displayed:
+     * <li>On long click, unless is not handled otherwise (by OnLongClickListener or a context
+     * menu). </li>
+     * <li>On hover, after a brief delay since the pointer has stopped moving </li>
+     *
+     * @param tooltip the tooltip text, or null if no tooltip is required
+     */
+    public final void setTooltip(@Nullable CharSequence tooltip) {
+        if (TextUtils.isEmpty(tooltip)) {
+            setFlags(0, TOOLTIP);
+            hideTooltip();
+            mTooltipInfo = null;
+        } else {
+            setFlags(TOOLTIP, TOOLTIP);
+            if (mTooltipInfo == null) {
+                mTooltipInfo = new TooltipInfo();
+                mTooltipInfo.mShowTooltipRunnable = this::showHoverTooltip;
+                mTooltipInfo.mHideTooltipRunnable = this::hideTooltip;
+            }
+            mTooltipInfo.mTooltip = tooltip;
+            if (mTooltipInfo.mTooltipPopup != null && mTooltipInfo.mTooltipPopup.isShowing()) {
+                mTooltipInfo.mTooltipPopup.updateContent(mTooltipInfo.mTooltip);
+            }
+        }
+    }
+
+    /**
+     * Returns the view's tooltip text.
+     *
+     * @return the tooltip text
+     */
+    @Nullable
+    public final CharSequence getTooltip() {
+        return mTooltipInfo != null ? mTooltipInfo.mTooltip : null;
+    }
+
+    private boolean showTooltip(int x, int y, boolean fromLongClick) {
+        if (mAttachInfo == null) {
+            return false;
+        }
+        if ((mViewFlags & ENABLED_MASK) != ENABLED) {
+            return false;
+        }
+        final CharSequence tooltipText = getTooltip();
+        if (TextUtils.isEmpty(tooltipText)) {
+            return false;
+        }
+        hideTooltip();
+        mTooltipInfo.mTooltipFromLongClick = fromLongClick;
+        mTooltipInfo.mTooltipPopup = new TooltipPopup(getContext());
+        mTooltipInfo.mTooltipPopup.show(this, x, y, tooltipText);
+        mAttachInfo.mTooltipHost = this;
+        return true;
+    }
+
+    void hideTooltip() {
+        if (mTooltipInfo == null) {
+            return;
+        }
+        removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+        if (mTooltipInfo.mTooltipPopup == null) {
+            return;
+        }
+        mTooltipInfo.mTooltipPopup.hide();
+        mTooltipInfo.mTooltipPopup = null;
+        mTooltipInfo.mTooltipFromLongClick = false;
+        if (mAttachInfo != null) {
+            mAttachInfo.mTooltipHost = null;
+        }
+    }
+
+    private boolean showLongClickTooltip(int x, int y) {
+        removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+        removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+        return showTooltip(x, y, true);
+    }
+
+    private void showHoverTooltip() {
+        showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false);
+    }
+
+    boolean dispatchTooltipHoverEvent(MotionEvent event) {
+        if (mTooltipInfo == null) {
+            return false;
+        }
+        switch(event.getAction()) {
+            case MotionEvent.ACTION_HOVER_MOVE:
+                if ((mViewFlags & TOOLTIP) != TOOLTIP || (mViewFlags & ENABLED_MASK) != ENABLED) {
+                    break;
+                }
+                if (!mTooltipInfo.mTooltipFromLongClick) {
+                    if (mTooltipInfo.mTooltipPopup == null) {
+                        // Schedule showing the tooltip after a timeout.
+                        mTooltipInfo.mAnchorX = (int) event.getX();
+                        mTooltipInfo.mAnchorY = (int) event.getY();
+                        removeCallbacks(mTooltipInfo.mShowTooltipRunnable);
+                        postDelayed(mTooltipInfo.mShowTooltipRunnable,
+                                ViewConfiguration.getHoverTooltipShowTimeout());
+                    }
+
+                    // Hide hover-triggered tooltip after a period of inactivity.
+                    // Match the timeout used by NativeInputManager to hide the mouse pointer
+                    // (depends on SYSTEM_UI_FLAG_LOW_PROFILE being set).
+                    final int timeout;
+                    if ((getWindowSystemUiVisibility() & SYSTEM_UI_FLAG_LOW_PROFILE)
+                            == SYSTEM_UI_FLAG_LOW_PROFILE) {
+                        timeout = ViewConfiguration.getHoverTooltipHideShortTimeout();
+                    } else {
+                        timeout = ViewConfiguration.getHoverTooltipHideTimeout();
+                    }
+                    removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+                    postDelayed(mTooltipInfo.mHideTooltipRunnable, timeout);
+                }
+                return true;
+
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (!mTooltipInfo.mTooltipFromLongClick) {
+                    hideTooltip();
+                }
+                break;
+        }
+        return false;
+    }
+
+    void handleTooltipKey(KeyEvent event) {
+        switch (event.getAction()) {
+            case KeyEvent.ACTION_DOWN:
+                if (event.getRepeatCount() == 0) {
+                    hideTooltip();
+                }
+                break;
+
+            case KeyEvent.ACTION_UP:
+                handleTooltipUp();
+                break;
+        }
+    }
+
+    private void handleTooltipUp() {
+        if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
+            return;
+        }
+        removeCallbacks(mTooltipInfo.mHideTooltipRunnable);
+        postDelayed(mTooltipInfo.mHideTooltipRunnable,
+                ViewConfiguration.getLongPressTooltipHideTimeout());
+    }
+
+    /**
+     * @return The content view of the tooltip popup currently being shown, or null if the tooltip
+     * is not showing.
+     * @hide
+     */
+    @TestApi
+    public View getTooltipView() {
+        if (mTooltipInfo == null || mTooltipInfo.mTooltipPopup == null) {
+            return null;
+        }
+        return mTooltipInfo.mTooltipPopup.getContentView();
+    }
 }
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 33b488f..6d2f850 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.TestApi;
 import android.app.AppGlobals;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -230,6 +231,29 @@
     private static final long ACTION_MODE_HIDE_DURATION_DEFAULT = 2000;
 
     /**
+     * Defines the duration in milliseconds before an end of a long press causes a tooltip to be
+     * hidden.
+     */
+    private static final int LONG_PRESS_TOOLTIP_HIDE_TIMEOUT = 1500;
+
+    /**
+     * Defines the duration in milliseconds before a hover event causes a tooltip to be shown.
+     */
+    private static final int HOVER_TOOLTIP_SHOW_TIMEOUT = 500;
+
+    /**
+     * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden.
+     * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set).
+     */
+    private static final int HOVER_TOOLTIP_HIDE_TIMEOUT = 15000;
+
+    /**
+     * Defines the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+     * (short version to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set).
+     */
+    private static final int HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT = 3000;
+
+    /**
      * Configuration values for overriding {@link #hasPermanentMenuKey()} behavior.
      * These constants must match the definition in res/values/config.xml.
      */
@@ -800,4 +824,43 @@
     public boolean isFadingMarqueeEnabled() {
         return mFadingMarqueeEnabled;
     }
+
+    /**
+     * @return the duration in milliseconds before an end of a long press causes a tooltip to be
+     * hidden
+     * @hide
+     */
+    @TestApi
+    public static int getLongPressTooltipHideTimeout() {
+        return LONG_PRESS_TOOLTIP_HIDE_TIMEOUT;
+    }
+
+    /**
+     * @return the duration in milliseconds before a hover event causes a tooltip to be shown
+     * @hide
+     */
+    @TestApi
+    public static int getHoverTooltipShowTimeout() {
+        return HOVER_TOOLTIP_SHOW_TIMEOUT;
+    }
+
+    /**
+     * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+     * (default variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is not set).
+     * @hide
+     */
+    @TestApi
+    public static int getHoverTooltipHideTimeout() {
+        return HOVER_TOOLTIP_HIDE_TIMEOUT;
+    }
+
+    /**
+     * @return the duration in milliseconds before mouse inactivity causes a tooltip to be hidden
+     * (shorter variant to be used when {@link View#SYSTEM_UI_FLAG_LOW_PROFILE} is set).
+     * @hide
+     */
+    @TestApi
+    public static int getHoverTooltipHideShortTimeout() {
+        return HOVER_TOOLTIP_HIDE_SHORT_TIMEOUT;
+    }
 }
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index e39cb96..c0191ce 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -199,6 +199,13 @@
     // It might not have actually handled the hover event.
     private boolean mHoveredSelf;
 
+    // The child capable of showing a tooltip and currently under the pointer.
+    private View mTooltipHoverTarget;
+
+    // True if the view group is capable of showing a tooltip and the pointer is directly
+    // over the view group but not one of its child views.
+    private boolean mTooltipHoveredSelf;
+
     /**
      * Internal flags.
      *
@@ -1970,6 +1977,104 @@
         }
     }
 
+    @Override
+    boolean dispatchTooltipHoverEvent(MotionEvent event) {
+        final int action = event.getAction();
+        switch (action) {
+            case MotionEvent.ACTION_HOVER_ENTER:
+                break;
+
+            case MotionEvent.ACTION_HOVER_MOVE:
+                View newTarget = null;
+
+                // Check what the child under the pointer says about the tooltip.
+                final int childrenCount = mChildrenCount;
+                if (childrenCount != 0) {
+                    final float x = event.getX();
+                    final float y = event.getY();
+
+                    final ArrayList<View> preorderedList = buildOrderedChildList();
+                    final boolean customOrder = preorderedList == null
+                            && isChildrenDrawingOrderEnabled();
+                    final View[] children = mChildren;
+                    for (int i = childrenCount - 1; i >= 0; i--) {
+                        final int childIndex =
+                                getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
+                        final View child =
+                                getAndVerifyPreorderedView(preorderedList, children, childIndex);
+                        final PointF point = getLocalPoint();
+                        if (isTransformedTouchPointInView(x, y, child, point)) {
+                            if (dispatchTooltipHoverEvent(event, child)) {
+                                newTarget = child;
+                            }
+                            break;
+                        }
+                    }
+                    if (preorderedList != null) preorderedList.clear();
+                }
+
+                if (mTooltipHoverTarget != newTarget) {
+                    if (mTooltipHoverTarget != null) {
+                        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+                        mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
+                        event.setAction(action);
+                    }
+                    mTooltipHoverTarget = newTarget;
+                }
+
+                if (mTooltipHoverTarget != null) {
+                    if (mTooltipHoveredSelf) {
+                        mTooltipHoveredSelf = false;
+                        event.setAction(MotionEvent.ACTION_HOVER_EXIT);
+                        super.dispatchTooltipHoverEvent(event);
+                        event.setAction(action);
+                    }
+                    return true;
+                }
+
+                mTooltipHoveredSelf = super.dispatchTooltipHoverEvent(event);
+                return mTooltipHoveredSelf;
+
+            case MotionEvent.ACTION_HOVER_EXIT:
+                if (mTooltipHoverTarget != null) {
+                    mTooltipHoverTarget.dispatchTooltipHoverEvent(event);
+                    mTooltipHoverTarget = null;
+                } else if (mTooltipHoveredSelf) {
+                    super.dispatchTooltipHoverEvent(event);
+                    mTooltipHoveredSelf = false;
+                }
+                break;
+        }
+        return false;
+    }
+
+    private boolean dispatchTooltipHoverEvent(MotionEvent event, View child) {
+        final boolean result;
+        if (!child.hasIdentityMatrix()) {
+            MotionEvent transformedEvent = getTransformedMotionEvent(event, child);
+            result = child.dispatchTooltipHoverEvent(transformedEvent);
+            transformedEvent.recycle();
+        } else {
+            final float offsetX = mScrollX - child.mLeft;
+            final float offsetY = mScrollY - child.mTop;
+            event.offsetLocation(offsetX, offsetY);
+            result = child.dispatchTooltipHoverEvent(event);
+            event.offsetLocation(-offsetX, -offsetY);
+        }
+        return result;
+    }
+
+    private void exitTooltipHoverTargets() {
+        if (mTooltipHoveredSelf || mTooltipHoverTarget != null) {
+            final long now = SystemClock.uptimeMillis();
+            MotionEvent event = MotionEvent.obtain(now, now,
+                    MotionEvent.ACTION_HOVER_EXIT, 0.0f, 0.0f, 0);
+            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+            dispatchTooltipHoverEvent(event);
+            event.recycle();
+        }
+    }
+
     /** @hide */
     @Override
     protected boolean hasHoveredChild() {
@@ -3186,6 +3291,7 @@
 
         // Similarly, set ACTION_EXIT to all hover targets and clear them.
         exitHoverTargets();
+        exitTooltipHoverTargets();
 
         // In case view is detached while transition is running
         mLayoutCalledWhileSuppressed = false;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1ff8fb0..e030e76 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -3589,6 +3589,10 @@
                         mAttachInfo.mKeyDispatchState.reset();
                         mView.dispatchWindowFocusChanged(hasWindowFocus);
                         mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus);
+
+                        if (mAttachInfo.mTooltipHost != null) {
+                            mAttachInfo.mTooltipHost.hideTooltip();
+                        }
                     }
 
                     // Note: must be done after the focus change callbacks,
@@ -4206,6 +4210,10 @@
         private int processKeyEvent(QueuedInputEvent q) {
             final KeyEvent event = (KeyEvent)q.mEvent;
 
+            if (mAttachInfo.mTooltipHost != null) {
+                mAttachInfo.mTooltipHost.handleTooltipKey(event);
+            }
+
             // If the key's purpose is to exit touch mode then we consume it
             // and consider it handled.
             if (checkForLeavingTouchModeAndConsume(event)) {
@@ -4232,6 +4240,10 @@
                 ensureTouchMode(true);
             }
 
+            if (action == MotionEvent.ACTION_DOWN && mAttachInfo.mTooltipHost != null) {
+                mAttachInfo.mTooltipHost.hideTooltip();
+            }
+
             // Offset the scroll position.
             if (mCurScrollY != 0) {
                 event.offsetLocation(0, mCurScrollY);
@@ -4425,6 +4437,7 @@
             mAttachInfo.mHandlingPointerEvent = true;
             boolean handled = eventTarget.dispatchPointerEvent(event);
             maybeUpdatePointerIcon(event);
+            maybeUpdateTooltip(event);
             mAttachInfo.mHandlingPointerEvent = false;
             if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                 mUnbufferedInputDispatch = true;
@@ -4512,6 +4525,27 @@
         return true;
     }
 
+    private void maybeUpdateTooltip(MotionEvent event) {
+        if (event.getPointerCount() != 1) {
+            return;
+        }
+        final int action = event.getActionMasked();
+        if (action != MotionEvent.ACTION_HOVER_ENTER
+                && action != MotionEvent.ACTION_HOVER_MOVE
+                && action != MotionEvent.ACTION_HOVER_EXIT) {
+            return;
+        }
+        AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
+        if (manager.isEnabled() && manager.isTouchExplorationEnabled()) {
+            return;
+        }
+        if (mView == null) {
+            Slog.d(mTag, "maybeUpdateTooltip called after view was removed");
+            return;
+        }
+        mView.dispatchTooltipHoverEvent(event);
+    }
+
     /**
      * Performs synthesis of new input events from unhandled input events.
      */
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 541fbe0..95fafc47 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -60,6 +60,8 @@
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.StaticLayout;
+import android.text.TextClassification;
+import android.text.TextSelection;
 import android.text.TextUtils;
 import android.text.method.KeyListener;
 import android.text.method.MetaKeyKeyListener;
@@ -147,16 +149,17 @@
     private static final String UNDO_OWNER_TAG = "Editor";
 
     // Ordering constants used to place the Action Mode or context menu items in their menu.
-    private static final int MENU_ITEM_ORDER_UNDO = 1;
-    private static final int MENU_ITEM_ORDER_REDO = 2;
-    private static final int MENU_ITEM_ORDER_CUT = 3;
-    private static final int MENU_ITEM_ORDER_COPY = 4;
-    private static final int MENU_ITEM_ORDER_PASTE = 5;
-    private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 6;
-    private static final int MENU_ITEM_ORDER_SHARE = 7;
-    private static final int MENU_ITEM_ORDER_SELECT_ALL = 8;
-    private static final int MENU_ITEM_ORDER_REPLACE = 9;
-    private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 10;
+    // Reserve 1 for the app that the ASSIST logic suggests as the best app to handle the selection.
+    private static final int MENU_ITEM_ORDER_UNDO = 2;
+    private static final int MENU_ITEM_ORDER_REDO = 3;
+    private static final int MENU_ITEM_ORDER_SHARE = 4;
+    private static final int MENU_ITEM_ORDER_CUT = 5;
+    private static final int MENU_ITEM_ORDER_COPY = 6;
+    private static final int MENU_ITEM_ORDER_PASTE = 7;
+    private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 8;
+    private static final int MENU_ITEM_ORDER_SELECT_ALL = 9;
+    private static final int MENU_ITEM_ORDER_REPLACE = 10;
+    private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 11;
 
     // Each Editor manages its own undo stack.
     private final UndoManager mUndoManager = new UndoManager();
@@ -3767,10 +3770,12 @@
 
         @Override
         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            TextClassification textClassification = updateSelectionWithTextAssistant();
+
             mode.setTitle(null);
             mode.setSubtitle(null);
             mode.setTitleOptionalHint(true);
-            populateMenuWithItems(menu);
+            populateMenuWithItems(menu, textClassification);
 
             Callback customCallback = getCustomCallback();
             if (customCallback != null) {
@@ -3796,13 +3801,30 @@
             }
         }
 
+        private TextClassification updateSelectionWithTextAssistant() {
+            // Trim the text so that only text necessary to provide context of the selected text is
+            // sent to the assistant.
+            CharSequence trimmedText = mTextView.getText();
+            int textLength = mTextView.getText().length();
+            int trimStartIndex = 0;
+            int startIndex = mTextView.getSelectionStart() - trimStartIndex;
+            int endIndex = mTextView.getSelectionEnd() - trimStartIndex;
+            TextSelection textSelection = mTextView.getTextAssistant()
+                    .suggestSelection(trimmedText, startIndex, endIndex);
+            Selection.setSelection(
+                    (Spannable) mTextView.getText(),
+                    Math.max(0, textSelection.getSelectionStartIndex() + trimStartIndex),
+                    Math.min(textLength, textSelection.getSelectionEndIndex() + trimStartIndex));
+            return textSelection.getTextClassification();
+        }
+
         private Callback getCustomCallback() {
             return mHasSelection
                     ? mCustomSelectionActionModeCallback
                     : mCustomInsertionActionModeCallback;
         }
 
-        private void populateMenuWithItems(Menu menu) {
+        private void populateMenuWithItems(Menu menu, TextClassification textClassification) {
             if (mTextView.canCut()) {
                 menu.add(Menu.NONE, TextView.ID_CUT, MENU_ITEM_ORDER_CUT,
                         com.android.internal.R.string.cut)
@@ -3827,11 +3849,12 @@
             if (mTextView.canShare()) {
                 menu.add(Menu.NONE, TextView.ID_SHARE, MENU_ITEM_ORDER_SHARE,
                         com.android.internal.R.string.share)
-                                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+                        .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
             }
 
             updateSelectAllItem(menu);
             updateReplaceItem(menu);
+            updateAssistMenuItem(menu, textClassification);
         }
 
         @Override
@@ -3870,6 +3893,12 @@
             }
         }
 
+        private void updateAssistMenuItem(Menu menu, TextClassification textClassification) {
+            // TODO: Find the best app available to handle the selected text based on information in
+            // the TextClassification object.
+            // Add app icon + intent to trigger app to the menu.
+        }
+
         @Override
         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
             if (mProcessTextIntentActionsHandler.performMenuItemAction(item)) {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 69f463c..b85175d 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -76,6 +76,7 @@
 import android.text.Spanned;
 import android.text.SpannedString;
 import android.text.StaticLayout;
+import android.text.TextAssistant;
 import android.text.TextDirectionHeuristic;
 import android.text.TextDirectionHeuristics;
 import android.text.TextPaint;
@@ -158,6 +159,7 @@
 import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Locale;
 
 /**
@@ -616,6 +618,7 @@
     private Rect mTempRect;
     private long mLastScroll;
     private Scroller mScroller;
+    private TextPaint mTempTextPaint;
 
     private BoringLayout.Metrics mBoring, mHintBoring;
     private BoringLayout mSavedLayout, mSavedHintLayout;
@@ -666,6 +669,20 @@
      */
     private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
 
+    // The TextView does not auto-size text.
+    public static final int AUTO_SIZE_TYPE_NONE = 0;
+    // The TextView performs uniform horizontal and vertical text size scaling to fit within the
+    // container.
+    public static final int AUTO_SIZE_TYPE_XY = 1;
+    // Auto-size type.
+    private int mAutoSizeType = AUTO_SIZE_TYPE_NONE;
+    // Default value for the step size in pixels.
+    private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1;
+    // Contains the sorted set of desired text sizes in pixels to pick from when auto-sizing text.
+    private int[] mAutoSizeTextSizesInPx;
+    // Specifies if the current TextView needs to be auto-sized.
+    private boolean mNeedsTextAutoResize = false;
+
     /**
      * Kick-start the font cache for the zygote process (to pay the cost of
      * initializing freetype for our default font only once).
@@ -868,6 +885,9 @@
         CharSequence hint = null;
         boolean password = false;
         int inputType = EditorInfo.TYPE_NULL;
+        int autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX;
+        int autoSizeMinTextSize = (int) TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics());
 
         a = theme.obtainStyledAttributes(
                     attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
@@ -1222,6 +1242,19 @@
                 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
                     mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
                     break;
+
+                case com.android.internal.R.styleable.TextView_autoSizeText:
+                    mAutoSizeType = a.getInt(attr, AUTO_SIZE_TYPE_NONE);
+                    break;
+
+                case com.android.internal.R.styleable.TextView_autoSizeStepGranularity:
+                    autoSizeStepGranularityInPx = a.getDimensionPixelSize(
+                            attr, DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX);
+                    break;
+
+                case com.android.internal.R.styleable.TextView_autoSizeMinTextSize:
+                    autoSizeMinTextSize = a.getDimensionPixelSize(attr, autoSizeMinTextSize);
+                    break;
             }
         }
         a.recycle();
@@ -1499,6 +1532,43 @@
         if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
         }
+
+        // Setup auto-size.
+        if (mEditor == null) {
+            switch (mAutoSizeType) {
+                case AUTO_SIZE_TYPE_NONE:
+                    // Nothing to do.
+                    break;
+                case AUTO_SIZE_TYPE_XY:
+                    // getTextSize() represents the maximum text size.
+                    if (getTextSize() <= autoSizeMinTextSize) {
+                        throw new IllegalStateException("Maximum text size is less then minimum "
+                                + "text size");
+                    }
+
+                    if (autoSizeStepGranularityInPx <= 0) {
+                        throw new IllegalStateException("Unexpected zero or negative value for auto"
+                                + " size step granularity in pixels");
+                    }
+
+                    final int autoSizeValuesLength = (int) ((getTextSize() - autoSizeMinTextSize)
+                            / autoSizeStepGranularityInPx);
+                    mAutoSizeTextSizesInPx = new int[autoSizeValuesLength];
+                    int sizeToAdd = autoSizeMinTextSize;
+                    for (int i = 0; i < autoSizeValuesLength; i++) {
+                        mAutoSizeTextSizesInPx[i] = sizeToAdd;
+                        sizeToAdd += autoSizeStepGranularityInPx;
+                    }
+
+                    Arrays.sort(mAutoSizeTextSizesInPx);
+                    mNeedsTextAutoResize = true;
+                    break;
+                default:
+                    throw new IllegalArgumentException(
+                            "Unknown autoSizeText type: " + mAutoSizeType);
+            }
+
+        }
     }
 
     private int[] parseDimensionArray(TypedArray dimens) {
@@ -2953,6 +3023,9 @@
 
     /**
      * @return the size (in pixels) of the default text size in this TextView.
+     *
+     * <p>Note: if this TextView has mAutoSizeType set to {@link TextView#AUTO_SIZE_TYPE_XY} than
+     * this function returns the maximum text size for auto-sizing.
      */
     @ViewDebug.ExportedProperty(category = "text")
     public float getTextSize() {
@@ -2985,6 +3058,9 @@
      * pixel" units.  This size is adjusted based on the current density and
      * user font size preference.
      *
+     * <p>Note: if this TextView has mAutoSizeType set to {@link TextView#AUTO_SIZE_TYPE_XY} than
+     * this function sets the maximum text size for auto-sizing.
+     *
      * @param size The scaled pixel size.
      *
      * @attr ref android.R.styleable#TextView_textSize
@@ -2998,6 +3074,9 @@
      * Set the default text size to a given unit and value.  See {@link
      * TypedValue} for the possible dimension units.
      *
+     * <p>Note: if this TextView has mAutoSizeType set to {@link TextView#AUTO_SIZE_TYPE_XY} than
+     * this function sets the maximum text size for auto-sizing.
+     *
      * @param unit The desired dimension unit.
      * @param size The desired size in the given units.
      *
@@ -7445,9 +7524,107 @@
             scrollTo(0, 0);
         }
 
+        if (mNeedsTextAutoResize) {
+            // Call auto-size after the width and height have been calculated.
+            autoSizeText();
+        }
+
         setMeasuredDimension(width, height);
     }
 
+    /**
+     * Automatically computes and sets the text size.
+     */
+    private void autoSizeText() {
+        synchronized (TEMP_RECTF) {
+            TEMP_RECTF.setEmpty();
+            final int maxWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
+            final int maxHeight = getMeasuredHeight() - getPaddingBottom() - getPaddingTop();
+
+            if (maxWidth <= 0 || maxHeight <= 0) {
+                return;
+            }
+
+            TEMP_RECTF.right = maxWidth;
+            TEMP_RECTF.bottom = maxHeight;
+            final float textSize = findLargestTextSizeWhichFits(TEMP_RECTF);
+            setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
+            mNeedsTextAutoResize = false;
+        }
+    }
+
+    /**
+     * Performs a binary search to find the largest text size that will still fit within the size
+     * available to this view.
+     */
+    private int findLargestTextSizeWhichFits(RectF availableSpace) {
+        final int sizesCount = mAutoSizeTextSizesInPx.length;
+        if (sizesCount == 0) {
+            throw new IllegalStateException("No available text sizes to choose from.");
+        }
+
+        int bestSizeIndex = 0;
+        int lowIndex = bestSizeIndex + 1;
+        int highIndex = sizesCount - 1;
+        int sizeToTryIndex;
+        while (lowIndex <= highIndex) {
+            sizeToTryIndex = (lowIndex + highIndex) / 2;
+            if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) {
+                bestSizeIndex = lowIndex;
+                lowIndex = sizeToTryIndex + 1;
+            } else {
+                highIndex = sizeToTryIndex - 1;
+                bestSizeIndex = highIndex;
+            }
+        }
+
+        return mAutoSizeTextSizesInPx[bestSizeIndex];
+    }
+
+    private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) {
+        final CharSequence text = getText();
+        final int maxLines = getMaxLines();
+        if (mTempTextPaint == null) {
+            mTempTextPaint = new TextPaint();
+        } else {
+            mTempTextPaint.reset();
+        }
+        mTempTextPaint.set(getPaint());
+        mTempTextPaint.setTextSize(suggestedSizeInPx);
+
+        if ((mLayout instanceof BoringLayout) && BoringLayout.isBoring(
+                text, mTempTextPaint, getTextDirectionHeuristic(), mBoring) != null) {
+            return mTempTextPaint.getFontSpacing() + getPaddingTop() + getPaddingBottom()
+                    <= availableSpace.bottom
+                    && mTempTextPaint.measureText(text, 0, text.length())
+                    + getPaddingLeft() + getPaddingRight() <= availableSpace.right;
+        } else {
+            StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(text, 0, text.length(),
+                    mTempTextPaint, getMeasuredWidth() - getPaddingLeft() - getPaddingRight());
+            layoutBuilder.setAlignment(getLayoutAlignment());
+            layoutBuilder.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier());
+            layoutBuilder.setIncludePad(true);
+            StaticLayout layout = layoutBuilder.build();
+
+            // Lines overflow.
+            if (maxLines != -1 && layout.getLineCount() > maxLines) {
+                return false;
+            }
+
+            // Width overflow.
+            if (layout.getWidth() > availableSpace.right) {
+                return false;
+            }
+
+            // Height overflow.
+            if (layout.getHeight() > availableSpace.bottom) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     private int getDesiredHeight() {
         return Math.max(
                 getDesiredHeight(mLayout, true),
@@ -9819,6 +9996,35 @@
         return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
     }
 
+    private TextAssistant mTextAssistant;
+
+    /**
+     * Sets the {@link TextAssistant} for this TextView.
+     * If null, this TextView uses the default TextAssistant which comes from the Activity.
+     */
+    public void setTextAssistant(TextAssistant textAssistant) {
+        mTextAssistant = textAssistant;
+    }
+
+    /**
+     * Returns the {@link TextAssistant} used by this TextView.
+     * If no TextAssistant is set, it'll use the one from this TextView's {@link Activity} or
+     * {@link Context}. If no TextAssistant is found, it'll use a no-op TextAssistant.
+     */
+    public TextAssistant getTextAssistant() {
+        if (mTextAssistant != null) {
+            return mTextAssistant;
+        }
+        if (mContext instanceof Activity) {
+            mTextAssistant = ((Activity) mContext).getTextAssistant();
+        } else {
+            // The context of this TextView should be an Activity. If it is not and no
+            // text assistant has been set, return a NO_OP TextAssistant.
+            mTextAssistant = TextAssistant.NO_OP;
+        }
+        return  mTextAssistant;
+    }
+
     /**
      * @hide
      */
diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java
index 594b6ab..c03bcdf 100644
--- a/core/java/com/android/internal/os/WrapperInit.java
+++ b/core/java/com/android/internal/os/WrapperInit.java
@@ -17,6 +17,8 @@
 package com.android.internal.os;
 
 import android.os.Process;
+import android.os.Trace;
+import android.util.BootTimingsTraceLog;
 import android.util.Slog;
 
 import dalvik.system.VMRuntime;
@@ -75,7 +77,8 @@
             }
 
             // Mimic system Zygote preloading.
-            ZygoteInit.preload();
+            ZygoteInit.preload(new BootTimingsTraceLog("WrapperInitTiming",
+                    Trace.TRACE_TAG_DALVIK));
 
             // Launch the application.
             String[] runtimeArgs = new String[args.length - 2];
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index e1118d8..cdd267e 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -36,11 +36,13 @@
 import android.system.Os;
 import android.system.OsConstants;
 import android.text.Hyphenator;
+import android.util.BootTimingsTraceLog;
 import android.util.EventLog;
 import android.util.Log;
 import android.webkit.WebViewFactory;
 import android.widget.TextView;
 
+import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.InstallerConnection.InstallerException;
 
 import dalvik.system.DexFile;
@@ -106,20 +108,20 @@
     private static final int ROOT_UID = 0;
     private static final int ROOT_GID = 0;
 
-    static void preload() {
+    static void preload(BootTimingsTraceLog bootTimingsTraceLog) {
         Log.d(TAG, "begin preload");
-        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "BeginIcuCachePinning");
+        bootTimingsTraceLog.traceBegin("BeginIcuCachePinning");
         beginIcuCachePinning();
-        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
-        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadClasses");
+        bootTimingsTraceLog.traceEnd(); // BeginIcuCachePinning
+        bootTimingsTraceLog.traceBegin("PreloadClasses");
         preloadClasses();
-        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
-        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadResources");
+        bootTimingsTraceLog.traceEnd(); // PreloadClasses
+        bootTimingsTraceLog.traceBegin("PreloadResources");
         preloadResources();
-        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
-        Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");
+        bootTimingsTraceLog.traceEnd(); // PreloadResources
+        bootTimingsTraceLog.traceBegin("PreloadOpenGL");
         preloadOpenGL();
-        Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+        bootTimingsTraceLog.traceEnd(); // PreloadOpenGL
         preloadSharedLibraries();
         preloadTextResources();
         // Ask the WebViewFactory to do any initialization that must run in the zygote process,
@@ -639,7 +641,13 @@
         }
 
         try {
-            Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ZygoteInit");
+            // Report Zygote start time to tron
+            MetricsLogger.histogram(null, "boot_zygote_init", (int) SystemClock.uptimeMillis());
+
+            String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";
+            BootTimingsTraceLog bootTimingsTraceLog = new BootTimingsTraceLog(bootTimeTag,
+                    Trace.TRACE_TAG_DALVIK);
+            bootTimingsTraceLog.traceBegin("ZygoteInit");
             RuntimeInit.enableDdms();
             // Start profiling the zygote initialization.
             SamplingProfilerIntegration.start();
@@ -664,22 +672,23 @@
             }
 
             zygoteServer.registerServerSocket(socketName);
-            Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "ZygotePreload");
+            bootTimingsTraceLog.traceBegin("ZygotePreload");
             EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                 SystemClock.uptimeMillis());
-            preload();
+            preload(bootTimingsTraceLog);
             EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                 SystemClock.uptimeMillis());
-            Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+            bootTimingsTraceLog.traceEnd(); // ZygotePreload
 
             // Finish profiling the zygote initialization.
             SamplingProfilerIntegration.writeZygoteSnapshot();
 
             // Do an initial gc to clean up after startup
-            Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PostZygoteInitGC");
+            bootTimingsTraceLog.traceBegin("PostZygoteInitGC");
             gcAndFinalize();
-            Trace.traceEnd(Trace.TRACE_TAG_DALVIK);
+            bootTimingsTraceLog.traceEnd(); // PostZygoteInitGC
 
+            bootTimingsTraceLog.traceEnd(); // ZygoteInit
             // Disable tracing so that forked processes do not inherit stale tracing tags from
             // Zygote.
             Trace.setTracingEnabled(false);
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index b0d45e1d1..be10608df 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -1190,6 +1190,26 @@
         }
 
         /**
+         * Remove a state from the state machine. Will not remove the state if it is currently
+         * active or if it has any children in the hierarchy.
+         * @param state the state to remove
+         */
+        private void removeState(State state) {
+            StateInfo stateInfo = mStateInfo.get(state);
+            if (stateInfo == null || stateInfo.active) {
+                return;
+            }
+            boolean isParent = mStateInfo.values().stream()
+                    .filter(si -> si.parentStateInfo == stateInfo)
+                    .findAny()
+                    .isPresent();
+            if (isParent) {
+                return;
+            }
+            mStateInfo.remove(state);
+        }
+
+        /**
          * Constructor
          *
          * @param looper for dispatching messages
@@ -1337,6 +1357,14 @@
     }
 
     /**
+     * Removes a state from the state machine, unless it is currently active or if it has children.
+     * @param state state to remove
+     */
+    public final void removeState(State state) {
+        mSmHandler.removeState(state);
+    }
+
+    /**
      * Set the initial state. This must be invoked before
      * and messages are sent to the state machine.
      *
diff --git a/core/java/com/android/internal/view/TooltipPopup.java b/core/java/com/android/internal/view/TooltipPopup.java
new file mode 100644
index 0000000..4f48b96
--- /dev/null
+++ b/core/java/com/android/internal/view/TooltipPopup.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.view;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+public class TooltipPopup {
+    private final Context mContext;
+
+    private final View mContentView;
+    private final TextView mMessageView;
+
+    private final WindowManager.LayoutParams mLayoutParams = new WindowManager.LayoutParams();
+    private final Rect mTmpDisplayFrame = new Rect();
+    private final int[] mTmpAnchorPos = new int[2];
+
+    public TooltipPopup(Context context) {
+        mContext = context;
+
+        mContentView = LayoutInflater.from(mContext).inflate(
+                com.android.internal.R.layout.tooltip, null);
+        mMessageView = (TextView) mContentView.findViewById(
+                com.android.internal.R.id.message);
+
+        mLayoutParams.setTitle(
+                mContext.getString(com.android.internal.R.string.tooltip_popup_title));
+        mLayoutParams.packageName = mContext.getOpPackageName();
+        mLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
+        mLayoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
+        mLayoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
+        mLayoutParams.format = PixelFormat.TRANSLUCENT;
+        mLayoutParams.windowAnimations = com.android.internal.R.style.Animation_Tooltip;
+        mLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+    }
+
+    public void show(View anchorView, int anchorX, int anchorY, CharSequence tooltipText) {
+        if (isShowing()) {
+            hide();
+        }
+
+        mMessageView.setText(tooltipText);
+
+        computePosition(anchorView, anchorX, anchorY, mLayoutParams);
+
+        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        wm.addView(mContentView, mLayoutParams);
+    }
+
+    public void hide() {
+        if (!isShowing()) {
+            return;
+        }
+
+        WindowManager wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
+        wm.removeView(mContentView);
+    }
+
+    public View getContentView() {
+        return mContentView;
+    }
+
+    public boolean isShowing() {
+        return mContentView.getParent() != null;
+    }
+
+    public void updateContent(CharSequence tooltipText) {
+        mMessageView.setText(tooltipText);
+    }
+
+    private void computePosition(View anchorView, int anchorX, int anchorY,
+            WindowManager.LayoutParams outParams) {
+        final int tooltipPreciseAnchorThreshold = mContext.getResources().getDimensionPixelOffset(
+                com.android.internal.R.dimen.tooltip_precise_anchor_threshold);
+
+        final int offsetX;
+        if (anchorView.getWidth() >= tooltipPreciseAnchorThreshold) {
+            // Wide view. Align the tooltip horizontally to the precise X position.
+            offsetX = anchorX;
+        } else {
+            // Otherwise anchor the tooltip to the view center.
+            offsetX = anchorView.getWidth() / 2;  // Center on the view horizontally.
+        }
+
+        final int offsetBelow;
+        final int offsetAbove;
+        if (anchorView.getHeight() >= tooltipPreciseAnchorThreshold) {
+            // Tall view. Align the tooltip vertically to the precise Y position.
+            offsetBelow = anchorY;
+            offsetAbove = anchorY;
+        } else {
+            // Otherwise anchor the tooltip to the view center.
+            offsetBelow = anchorView.getHeight();  // Place below the view in most cases.
+            offsetAbove = 0;  // Place above the view if the tooltip does not fit below.
+        }
+
+        outParams.gravity = Gravity.CENTER_HORIZONTAL | Gravity.TOP;
+
+        final int tooltipOffset = mContext.getResources().getDimensionPixelOffset(
+                com.android.internal.R.dimen.tooltip_y_offset);
+
+        anchorView.getWindowVisibleDisplayFrame(mTmpDisplayFrame);
+        anchorView.getLocationInWindow(mTmpAnchorPos);
+        outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2;
+        outParams.y = mTmpAnchorPos[1] + offsetBelow + tooltipOffset;
+
+        final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+        mContentView.measure(spec, spec);
+        final int tooltipHeight = mContentView.getMeasuredHeight();
+
+        if (outParams.y + tooltipHeight > mTmpDisplayFrame.height()) {
+            // The tooltip does not fit below the anchor point, show above instead.
+            outParams.y = mTmpAnchorPos[1] + offsetAbove - (tooltipOffset + tooltipHeight);
+        }
+    }
+}
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
index 293b77b..20230cd 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -41,6 +41,8 @@
     private String mLastPackage;
     private int mLastResId;
     private boolean mInternalSetDrawable;
+    private boolean mForceHidden;
+    private int mDesiredVisibility;
 
     public CachingIconView(Context context, @Nullable AttributeSet attrs) {
         super(context, attrs);
@@ -175,4 +177,24 @@
         mLastResId = 0;
         mLastPackage = null;
     }
+
+    /**
+     * Set the icon to be forcibly hidden, even when it's visibility is changed to visible.
+     */
+    public void setForceHidden(boolean forceHidden) {
+        mForceHidden = forceHidden;
+        updateVisibility();
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        mDesiredVisibility = visibility;
+        updateVisibility();
+    }
+
+    private void updateVisibility() {
+        int visibility = mDesiredVisibility == VISIBLE && mForceHidden ? INVISIBLE
+                : mDesiredVisibility;
+        super.setVisibility(visibility);
+    }
 }
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index b0bc81b..63b700b 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -1432,7 +1432,8 @@
                         STRONG_AUTH_REQUIRED_AFTER_BOOT,
                         STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW,
                         SOME_AUTH_REQUIRED_AFTER_USER_REQUEST,
-                        STRONG_AUTH_REQUIRED_AFTER_LOCKOUT})
+                        STRONG_AUTH_REQUIRED_AFTER_LOCKOUT,
+                        STRONG_AUTH_REQUIRED_AFTER_TIMEOUT})
         @Retention(RetentionPolicy.SOURCE)
         public @interface StrongAuthFlags {}
 
@@ -1463,6 +1464,12 @@
         public static final int STRONG_AUTH_REQUIRED_AFTER_LOCKOUT = 0x8;
 
         /**
+         * Strong authentication is required because it hasn't been used for a time required by
+         * a device admin.
+         */
+        public static final int STRONG_AUTH_REQUIRED_AFTER_TIMEOUT = 0x10;
+
+        /**
          * Strong auth flags that do not prevent fingerprint from being accepted as auth.
          *
          * If any other flags are set, fingerprint is disabled.
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 1ded2c5..2923f94 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -235,6 +235,7 @@
     int sampleSize = 1;
     bool onlyDecodeSize = false;
     SkColorType prefColorType = kN32_SkColorType;
+    bool isHardware = false;
     bool isMutable = false;
     float scale = 1.0f;
     bool requireUnpremultiplied = false;
@@ -260,6 +261,7 @@
 
         jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
         prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
+        isHardware = GraphicsJNI::isHardwareConfig(env, jconfig);
         isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
         requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
         javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
@@ -352,9 +354,10 @@
         decodeAllocator = &scaleCheckingAllocator;
     } else if (javaBitmap != nullptr) {
         decodeAllocator = &recyclingAllocator;
-    } else if (willScale) {
-        // This will allocate pixels using a HeapAllocator, since there will be an extra
-        // scaling step.
+    } else if (willScale || isHardware) {
+        // This will allocate pixels using a HeapAllocator,
+        // for scale case: there will be an extra scaling step.
+        // for hardware case: there will be extra swizzling & upload to gralloc step.
         decodeAllocator = &heapAllocator;
     } else {
         decodeAllocator = &defaultAllocator;
@@ -539,6 +542,12 @@
     if (isMutable) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Mutable;
     if (isPremultiplied) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Premultiplied;
 
+    if (isHardware) {
+        sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(outputBitmap);
+        return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags,
+                ninePatchChunk, ninePatchInsets, -1);
+    }
+
     // now create the java bitmap
     return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(),
             bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index 115ee72..3b2d5d2 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -131,12 +131,13 @@
     SkColorType colorType = kN32_SkColorType;
     bool requireUnpremul = false;
     jobject javaBitmap = NULL;
-
+    bool isHardware = false;
     // Update the default options with any options supplied by the client.
     if (NULL != options) {
         sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
         jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
         colorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
+        isHardware = GraphicsJNI::isHardwareConfig(env, jconfig);
         requireUnpremul = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
         javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
         // The Java options of ditherMode and preferQualityOverSpeed are deprecated.  We will
@@ -202,6 +203,10 @@
     if (!requireUnpremul) {
         bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Premultiplied;
     }
+    if (isHardware) {
+        sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(bitmap);
+        return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags);
+    }
     return android::bitmap::createBitmap(env, heapAlloc.getStorageObjAndReset(), bitmapCreateFlags);
 }
 
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index 322eed5..e740428 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -297,8 +297,9 @@
     kRGB_565_LegacyBitmapConfig     = 3,
     kARGB_4444_LegacyBitmapConfig   = 4,
     kARGB_8888_LegacyBitmapConfig   = 5,
+    kHardware_LegacyBitmapConfig    = 6,
 
-    kLastEnum_LegacyBitmapConfig = kARGB_8888_LegacyBitmapConfig
+    kLastEnum_LegacyBitmapConfig = kHardware_LegacyBitmapConfig
 };
 
 jint GraphicsJNI::colorTypeToLegacyBitmapConfig(SkColorType colorType) {
@@ -327,6 +328,7 @@
         kIndex_8_SkColorType,
         kRGB_565_SkColorType,
         kARGB_4444_SkColorType,
+        kN32_SkColorType,
         kN32_SkColorType
     };
 
@@ -355,6 +357,15 @@
     return legacyBitmapConfigToColorType(c);
 }
 
+bool GraphicsJNI::isHardwareConfig(JNIEnv* env, jobject jconfig) {
+    SkASSERT(env);
+    if (NULL == jconfig) {
+        return false;
+    }
+    int c = env->GetIntField(jconfig, gBitmapConfig_nativeInstanceID);
+    return c == kHardware_LegacyBitmapConfig;
+}
+
 android::Canvas* GraphicsJNI::getNativeCanvas(JNIEnv* env, jobject canvas) {
     SkASSERT(env);
     SkASSERT(canvas);
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index aaa8db9..ced9939 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -68,6 +68,8 @@
     */
     static SkColorType getNativeBitmapColorType(JNIEnv*, jobject jconfig);
 
+    static bool isHardwareConfig(JNIEnv* env, jobject jconfig);
+
     static jobject createRegion(JNIEnv* env, SkRegion* region);
 
     static jobject createBitmapRegionDecoder(JNIEnv* env, SkBitmapRegionDecoder* bitmap);
diff --git a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
index 4b279f63..b23757e 100644
--- a/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
+++ b/core/jni/android_hardware_location_ActivityRecognitionHardware.cpp
@@ -22,256 +22,89 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/Log.h>
 
-#include "activity_recognition.h"
-
-
-// keep base connection data from the HAL
-static activity_recognition_module_t* sModule = NULL;
-static activity_recognition_device_t* sDevice = NULL;
-
-static jobject sCallbacksObject = NULL;
-static jmethodID sOnActivityChanged = NULL;
-
-
-static void check_and_clear_exceptions(JNIEnv* env, const char* method_name) {
-    if (!env->ExceptionCheck()) {
-        return;
-    }
-
-    ALOGE("An exception was thrown by '%s'.", method_name);
-    LOGE_EX(env);
-    env->ExceptionClear();
-}
-
-static jint attach_thread(JNIEnv** env) {
-    JavaVM* java_vm = android::AndroidRuntime::getJavaVM();
-    assert(java_vm != NULL);
-
-    JavaVMAttachArgs args = {
-        JNI_VERSION_1_6,
-        "ActivityRecognition HAL callback.",
-        NULL /* group */
-    };
-
-    jint result = java_vm->AttachCurrentThread(env, &args);
-    if (result != JNI_OK) {
-        ALOGE("Attach to callback thread failed: %d", result);
-    }
-
-    return result;
-}
-
-static jint detach_thread() {
-    JavaVM* java_vm = android::AndroidRuntime::getJavaVM();
-    assert(java_vm != NULL);
-
-    jint result = java_vm->DetachCurrentThread();
-    if (result != JNI_OK) {
-        ALOGE("Detach of callback thread failed: %d", result);
-    }
-
-    return result;
-}
-
-
-/**
- * Handle activity recognition events from HAL.
- */
-static void activity_callback(
-        const activity_recognition_callback_procs_t* procs,
-        const activity_event_t* events,
-        int count) {
-    if (sOnActivityChanged == NULL) {
-        ALOGE("Dropping activity_callback because onActivityChanged handler is null.");
-        return;
-    }
-
-    if (events == NULL || count <= 0) {
-        ALOGE("Invalid activity_callback. Count: %d, Events: %p", count, events);
-        return;
-    }
-
-    JNIEnv* env = NULL;
-    int result = attach_thread(&env);
-    if (result != JNI_OK) {
-        ALOGE("Unable to attach thread with JNI.");
-        return;
-    }
-
-    jclass event_class =
-            env->FindClass("android/hardware/location/ActivityRecognitionHardware$Event");
-    jmethodID event_ctor = env->GetMethodID(event_class, "<init>", "()V");
-    jfieldID activity_field = env->GetFieldID(event_class, "activity", "I");
-    jfieldID type_field = env->GetFieldID(event_class, "type", "I");
-    jfieldID timestamp_field = env->GetFieldID(event_class, "timestamp", "J");
-
-    jobjectArray events_array = env->NewObjectArray(count, event_class, NULL);
-    for (int i = 0; i < count; ++i) {
-        const activity_event_t* event = &events[i];
-        jobject event_object = env->NewObject(event_class, event_ctor);
-        env->SetIntField(event_object, activity_field, event->activity);
-        env->SetIntField(event_object, type_field, event->event_type);
-        env->SetLongField(event_object, timestamp_field, event->timestamp);
-        env->SetObjectArrayElement(events_array, i, event_object);
-        env->DeleteLocalRef(event_object);
-    }
-
-    env->CallVoidMethod(sCallbacksObject, sOnActivityChanged, events_array);
-    check_and_clear_exceptions(env, __FUNCTION__);
-
-    // TODO: ideally we'd let the HAL register the callback thread only once
-    detach_thread();
-}
-
-activity_recognition_callback_procs_t sCallbacks = {
-    activity_callback,
-};
+// #include "activity_recognition.h"
+// The activity recognition HAL is being deprecated. This means -
+//    i) Android framework code shall not depend on activity recognition
+//       being provided through the activity_recognition.h interface.
+//   ii) activity recognition HAL will not be binderized as the other HALs.
+//
 
 /**
  * Initializes the ActivityRecognitionHardware class from the native side.
  */
-static void class_init(JNIEnv* env, jclass clazz) {
-    // open the hardware module
-    int error = hw_get_module(
-            ACTIVITY_RECOGNITION_HARDWARE_MODULE_ID,
-            (const hw_module_t**) &sModule);
-    if (error != 0) {
-        ALOGE("Error hw_get_module: %d", error);
-        return;
-    }
-
-    error = activity_recognition_open(&sModule->common, &sDevice);
-    if (error != 0) {
-        ALOGE("Error opening device: %d", error);
-        return;
-    }
-
-    // get references to the Java provided methods
-    sOnActivityChanged = env->GetMethodID(
-            clazz,
-            "onActivityChanged",
-            "([Landroid/hardware/location/ActivityRecognitionHardware$Event;)V");
-    if (sOnActivityChanged == NULL) {
-        ALOGE("Error obtaining ActivityChanged callback.");
-        return;
-    }
-
-    // register callbacks
-    sDevice->register_activity_callback(sDevice, &sCallbacks);
+static void class_init(JNIEnv* /*env*/, jclass /*clazz*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
 }
 
 /**
  * Initializes and connect the callbacks handlers in the HAL.
  */
-static void initialize(JNIEnv* env, jobject obj) {
-    if (sCallbacksObject == NULL) {
-        sCallbacksObject = env->NewGlobalRef(obj);
-    } else {
-        ALOGD("Callbacks Object was already initialized.");
-    }
-
-    if (sDevice != NULL) {
-        sDevice->register_activity_callback(sDevice, &sCallbacks);
-    } else {
-        ALOGD("ActivityRecognition device not found during initialization.");
-    }
+static void initialize(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
 }
 
 /**
  * De-initializes the ActivityRecognitionHardware from the native side.
  */
-static void release(JNIEnv* env, jobject obj) {
-    if (sDevice == NULL) {
-        return;
-    }
-
-    int error = activity_recognition_close(sDevice);
-    if (error != 0) {
-        ALOGE("Error closing device: %d", error);
-        return;
-    }
+static void release(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
 }
 
 /**
  * Returns true if ActivityRecognition HAL is supported, false otherwise.
  */
-static jboolean is_supported(JNIEnv* env, jclass clazz) {
-    if (sModule != NULL && sDevice != NULL ) {
-        return JNI_TRUE;
-    }
+static jboolean is_supported(JNIEnv* /*env*/, jclass /*clazz*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
     return JNI_FALSE;
 }
 
 /**
  * Gets an array representing the supported activities.
  */
-static jobjectArray get_supported_activities(JNIEnv* env, jobject obj) {
-    if (sModule == NULL) {
-        return NULL;
-    }
-
-    char const* const* list = NULL;
-    int list_size = sModule->get_supported_activities_list(sModule, &list);
-    if (list_size <= 0 || list == NULL) {
-        return NULL;
-    }
-
-    jclass string_class = env->FindClass("java/lang/String");
-    if (string_class == NULL) {
-        ALOGE("Unable to find String class for supported activities.");
-        return NULL;
-    }
-
-    jobjectArray string_array = env->NewObjectArray(list_size, string_class, NULL);
-    if (string_array == NULL) {
-        ALOGE("Unable to create string array for supported activities.");
-        return NULL;
-    }
-
-    for (int i = 0; i < list_size; ++i) {
-        const char* string_ptr = const_cast<const char*>(list[i]);
-        jstring string = env->NewStringUTF(string_ptr);
-        env->SetObjectArrayElement(string_array, i, string);
-    }
-
-    return string_array;
+static jobjectArray get_supported_activities(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return NULL;
 }
 
 /**
  * Enables a given activity event to be actively monitored.
  */
 static int enable_activity_event(
-        JNIEnv* env,
-        jobject obj,
-        jint activity_handle,
-        jint event_type,
-        jlong report_latency_ns) {
-    return sDevice->enable_activity_event(
-            sDevice,
-            (uint32_t) activity_handle,
-            (uint32_t) event_type,
-            report_latency_ns);
+        JNIEnv* /*env*/,
+        jobject /*obj*/,
+        jint /*activity_handle*/,
+        jint /*event_type*/,
+        jlong /*report_latency_ns*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return android::NO_INIT;
 }
 
 /**
  * Disables a given activity event from being actively monitored.
  */
 static int disable_activity_event(
-        JNIEnv* env,
-        jobject obj,
-        jint activity_handle,
-        jint event_type) {
-    return sDevice->disable_activity_event(
-            sDevice,
-            (uint32_t) activity_handle,
-            (uint32_t) event_type);
+        JNIEnv* /*env*/,
+        jobject /*obj*/,
+        jint /*activity_handle*/,
+        jint /*event_type*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return android::NO_INIT;
 }
 
 /**
  * Request flush for al batch buffers.
  */
-static int flush(JNIEnv* env, jobject obj) {
-    return sDevice->flush(sDevice);
+static int flush(JNIEnv* /*env*/, jobject /*obj*/) {
+    ALOGE("activity_recognition HAL is deprecated. %s is effectively a no-op",
+          __FUNCTION__);
+    return android::NO_INIT;
 }
 
 
diff --git a/core/jni/android_os_SystemProperties.cpp b/core/jni/android_os_SystemProperties.cpp
index 5dace6b..8844fb0 100644
--- a/core/jni/android_os_SystemProperties.cpp
+++ b/core/jni/android_os_SystemProperties.cpp
@@ -220,6 +220,11 @@
     }
 }
 
+static void SystemProperties_report_sysprop_change(JNIEnv /**env*/, jobject /*clazz*/)
+{
+    report_sysprop_change();
+}
+
 static const JNINativeMethod method_table[] = {
     { "native_get", "(Ljava/lang/String;)Ljava/lang/String;",
       (void*) SystemProperties_getS },
@@ -235,6 +240,8 @@
       (void*) SystemProperties_set },
     { "native_add_change_callback", "()V",
       (void*) SystemProperties_add_change_callback },
+    { "native_report_sysprop_change", "()V",
+      (void*) SystemProperties_report_sysprop_change },
 };
 
 int register_android_os_SystemProperties(JNIEnv *env)
diff --git a/core/res/res/anim/tooltip_enter.xml b/core/res/res/anim/tooltip_enter.xml
new file mode 100644
index 0000000..7eceb4c
--- /dev/null
+++ b/core/res/res/anim/tooltip_enter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@interpolator/decelerate_quad"
+        android:fromAlpha="0.0" android:toAlpha="1.0"
+        android:duration="@android:integer/config_tooltipAnimTime" />
diff --git a/core/res/res/anim/tooltip_exit.xml b/core/res/res/anim/tooltip_exit.xml
new file mode 100644
index 0000000..e346ca9
--- /dev/null
+++ b/core/res/res/anim/tooltip_exit.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+        android:interpolator="@interpolator/accelerate_quad"
+        android:fromAlpha="1.0" android:toAlpha="0.0"
+        android:duration="@android:integer/config_tooltipAnimTime" />
diff --git a/core/res/res/drawable/tooltip_frame.xml b/core/res/res/drawable/tooltip_frame.xml
new file mode 100644
index 0000000..14130c8
--- /dev/null
+++ b/core/res/res/drawable/tooltip_frame.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <solid android:color="?attr/tooltipBackgroundColor" />
+    <corners android:radius="@dimen/tooltip_corner_radius" />
+</shape>
\ No newline at end of file
diff --git a/core/res/res/layout/tooltip.xml b/core/res/res/layout/tooltip.xml
new file mode 100644
index 0000000..0aa6a87
--- /dev/null
+++ b/core/res/res/layout/tooltip.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@android:id/message"
+        android:layout_width="wrap_content"
+        android:layout_height="@dimen/tooltip_height"
+        android:layout_margin="@dimen/tooltip_margin"
+        android:paddingStart="@dimen/tooltip_horizontal_padding"
+        android:paddingEnd="@dimen/tooltip_horizontal_padding"
+        android:gravity="center"
+        android:background="?android:attr/tooltipFrameBackground"
+        android:textAppearance="@style/TextAppearance.Tooltip"
+        android:textColor="?android:attr/tooltipForegroundColor"
+        android:singleLine="true"
+        android:ellipsize="end"
+    />
+
+</LinearLayout>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index e43f1ba..b2212f9 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Jy sal enige veranderinge verloor en die demonstrasie sal oor <xliff:g id="TIMEOUT">%1$s</xliff:g> sekondes weer begin …"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Kanselleer"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Stel nou terug"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Doen \'n fabriekterugstelling om hierdie toestel sonder beperkinge te gebruik"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Raak om meer te wete te kom."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Het <xliff:g id="LABEL">%1$s</xliff:g> gedeaktiveer"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferensie-oproep"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index af8c592..2b92730 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ማንኛቸውም ለውጦች ይጠፋሉ፣ እና ማሳያው በ<xliff:g id="TIMEOUT">%1$s</xliff:g> ሰከንዶች ውስጥ እንደገና ይጀምራል…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ይቅር"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"አሁን ዳግም አስጀምር"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"ይህን መሣሪያ ያለምንም ገደብ ለመጠቀም የፋብሪካ ዳግም ያስጀምሩ"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"የበለጠ ለመረዳት ይንኩ።"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> ተሰናክሏል"</string>
     <string name="conference_call" msgid="3751093130790472426">"የስብሰባ ጥሪ"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index b3c24e5..b2835cc 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -1825,8 +1825,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ستفقد أي تغييرات وسيبدأ العرض التوضيحي مرة أخرى خلال <xliff:g id="TIMEOUT">%1$s</xliff:g> من الثواني…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"إلغاء"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"إعادة التعيين الآن"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"يمكنك إعادة تعيين بيانات المصنع لاستخدام هذا الجهاز بدون قيود"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"المس للتعرف على مزيد من المعلومات."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"تم تعطيل <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"مكالمة جماعية"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-az-rAZ/strings.xml b/core/res/res/values-az-rAZ/strings.xml
index 7e9c508..5d09077 100644
--- a/core/res/res/values-az-rAZ/strings.xml
+++ b/core/res/res/values-az-rAZ/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Hər hansı dəyişikliyi itirəcəksiniz və demo <xliff:g id="TIMEOUT">%1$s</xliff:g> saniyəyə yenidən başlayacaq…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Ləğv edin"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"İndi sıfırlayın"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Bu cihazı məhdudiyyətsiz istifadə etmək üçün zavod sıfırlaması edin"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Daha çox məlumat üçün toxunun."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> deaktiv edildi"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konfrans Zəngi"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 6caf561..b65ed4d 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -1717,8 +1717,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Izgubićete sve promene i demonstracija će ponovo početi za <xliff:g id="TIMEOUT">%1$s</xliff:g> sek…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Otkaži"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Resetuj"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Resetujte uređaj na fabrička podešavanja da biste ga koristili bez ograničenja"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Dodirnite da biste saznali više."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Vidžet <xliff:g id="LABEL">%1$s</xliff:g> je onemogućen"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferencijski poziv"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-be-rBY/strings.xml b/core/res/res/values-be-rBY/strings.xml
index c076db2..63359a1 100644
--- a/core/res/res/values-be-rBY/strings.xml
+++ b/core/res/res/values-be-rBY/strings.xml
@@ -1753,8 +1753,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Усе змены будуць страчаны, і дэманстрацыя пачнецца зноў праз <xliff:g id="TIMEOUT">%1$s</xliff:g> с…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Скасаваць"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Выканаць скід"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Выканайце скід да заводскіх налад, каб выкарыстоўваць гэту прыладу без абмежаванняў"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Краніце, каб даведацца больш."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Адключаны <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Канферэнц-выклік"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index 2d6bdb2..a818b7e 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Ще загубите всички промени и демонстрацията ще започне отново след <xliff:g id="TIMEOUT">%1$s</xliff:g> секунди…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Отказ"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Нулиране сега"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Възстановете фабричните настройки на това устройство, за да го използвате без ограничения"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Докоснете, за да научите повече."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g>: Деактивирано"</string>
     <string name="conference_call" msgid="3751093130790472426">"Конферентно обаждане"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-bn-rBD/strings.xml b/core/res/res/values-bn-rBD/strings.xml
index 21b737a..20343af 100644
--- a/core/res/res/values-bn-rBD/strings.xml
+++ b/core/res/res/values-bn-rBD/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"আপনার করা যে কোনো পরিবর্তন মুছে যাবে এবং <xliff:g id="TIMEOUT">%1$s</xliff:g> সেকেন্ডের মধ্যে ডেমো আবার শুরু হবে…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"বাতিল করুন"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"এখনই পুনরায় সেট করুন"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"কোনো বিধিনিষেধ ছাড়াই এই ডিভাইসটিকে ব্যবহার করতে ফ্যাক্টরি রিসেট করুন"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"আরো জানতে স্পর্শ করুন৷"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"অক্ষম করা <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"কনফারেন্স কল"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-bs-rBA/strings.xml b/core/res/res/values-bs-rBA/strings.xml
index 80ee407..205bffb 100644
--- a/core/res/res/values-bs-rBA/strings.xml
+++ b/core/res/res/values-bs-rBA/strings.xml
@@ -1719,8 +1719,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Nestat će sve izmjene, a demonstracija će početi ponovo za <xliff:g id="TIMEOUT">%1$s</xliff:g> sek…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Otkaži"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Vrati sada na početne postavke"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Vratite uređaj na fabričke postavke kako biste ga koristili bez ograničenja"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Dodirnite da saznate više."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Onemogućen <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferencijski poziv"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 35b2918..a1797cb 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Perdràs els canvis, i la demostració tornarà a començar d\'aquí a <xliff:g id="TIMEOUT">%1$s</xliff:g> segons…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancel·la"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Restableix ara"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Restableix les dades de fàbrica del dispositiu per utilitzar-lo sense restriccions"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toca per obtenir més informació."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> s\'ha desactivat"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conferència"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 95a6728..2c8f843 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -1753,8 +1753,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Ztratíte všechny provedené změny a ukázka se za <xliff:g id="TIMEOUT">%1$s</xliff:g> s spustí znovu…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Zrušit"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Resetovat"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Chcete-li toto zařízení používat bez omezení, obnovte jej do továrního nastavení"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Klepnutím zobrazíte další informace."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> – zakázáno"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferenční hovor"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index a628e42..20b859c 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Du mister alle ændringer, og demoen starter igen om <xliff:g id="TIMEOUT">%1$s</xliff:g> sekunder…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Annuller"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Nulstil nu"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Gendan fabriksdataene på enheden for at bruge den uden begrænsninger"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Tryk for at få flere oplysninger."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> – deaktiveret"</string>
     <string name="conference_call" msgid="3751093130790472426">"Telefonmøde"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 86f2070..fcad7c2 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Alle Änderungen gehen verloren und Demo wird in <xliff:g id="TIMEOUT">%1$s</xliff:g> Sekunden neu gestartet…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Abbrechen"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Jetzt zurücksetzen"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Gerät auf Werkseinstellungen zurücksetzen, um es ohne Einschränkungen zu nutzen"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Für weitere Informationen tippen."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> deaktiviert"</string>
     <string name="conference_call" msgid="3751093130790472426">"Telefonkonferenz"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index 4ce3a1f..2117233 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Τυχόν αλλαγές που πραγματοποιήσατε θα χαθούν και η επίδειξη θα ξεκινήσει ξανά σε <xliff:g id="TIMEOUT">%1$s</xliff:g> δευτερόλεπτα…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Ακύρωση"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Επαναφορά τώρα"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Επαναφέρετε τις εργοστασιακές ρυθμίσεις για να χρησιμοποιήσετε αυτήν τη συσκευή χωρίς περιορισμούς"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Αγγίξτε για να μάθετε περισσότερα."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Απενεργοποιημένο <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Κλήση συνδιάσκεψης"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 72c1271..47be8cf 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"You\'ll lose any changes and the demo will start again in <xliff:g id="TIMEOUT">%1$s</xliff:g> seconds…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancel"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Reset now"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Factory reset to use this device without restrictions"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Touch to find out more."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Disabled <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conference Call"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 72c1271..47be8cf 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"You\'ll lose any changes and the demo will start again in <xliff:g id="TIMEOUT">%1$s</xliff:g> seconds…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancel"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Reset now"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Factory reset to use this device without restrictions"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Touch to find out more."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Disabled <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conference Call"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 72c1271..47be8cf 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"You\'ll lose any changes and the demo will start again in <xliff:g id="TIMEOUT">%1$s</xliff:g> seconds…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancel"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Reset now"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Factory reset to use this device without restrictions"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Touch to find out more."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Disabled <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conference Call"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 738af8c..50c4a67 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Se perderán los cambios y la demostración volverá a iniciarse en <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Restablecer ahora"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Restablece la configuración de fábrica para usar este dispositivo sin restricciones"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toca para obtener más información."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Se inhabilitó <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conferencia"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 3c2915d..a91fc1c 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Se perderán todos los cambios y la demostración volverá a empezar en <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Restablecer ahora"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Restablece los datos de fábrica para usar este dispositivo sin restricciones"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toca para obtener más información."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> inhabilitado"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conferencia"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-et-rEE/strings.xml b/core/res/res/values-et-rEE/strings.xml
index 96b060f..dc926d4 100644
--- a/core/res/res/values-et-rEE/strings.xml
+++ b/core/res/res/values-et-rEE/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Kõik muudatused lähevad kaotsi ja demo käivitub uuesti <xliff:g id="TIMEOUT">%1$s</xliff:g> sekundi möödudes …"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Tühista"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Lähtesta kohe"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Seadme piiranguteta kasutamiseks lähtestage see tehaseandmetele"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Lisateabe saamiseks puudutage."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Keelatud <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konverentskõne"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-eu-rES/strings.xml b/core/res/res/values-eu-rES/strings.xml
index 6ccd864..190d61b 100644
--- a/core/res/res/values-eu-rES/strings.xml
+++ b/core/res/res/values-eu-rES/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Aldaketak galduko dituzu eta <xliff:g id="TIMEOUT">%1$s</xliff:g> segundo barru hasiko da berriro demoa…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Utzi"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Berrezarri"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Berrezarri jatorrizko ezarpenak gailua murriztapenik gabe erabili ahal izateko"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Sakatu informazio gehiago lortzeko."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> desgaituta dago"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferentzia-deia"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index e8e61f4..d1d120f 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"همه تغییرات را از دست خواهید داد و نسخه نمایشی دوباره تا <xliff:g id="TIMEOUT">%1$s</xliff:g> ثانیه دیگر شروع می‌شود…‏"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"لغو"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"بازنشانی در این لحظه"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"برای استفاده بدون محدودیت از این دستگاه، بازنشانی کارخانه‌ای انجام دهید"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"برای یادگیری بیشتر لمس کنید."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> غیرفعال شد"</string>
     <string name="conference_call" msgid="3751093130790472426">"تماس کنفرانسی"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index dbfd8ba..425b235 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Muutokset poistetaan ja esittely aloitetaan uudelleen <xliff:g id="TIMEOUT">%1$s</xliff:g> sekunnin kuluttua…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Peruuta"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Palauta nyt"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Palauta tehdasasetukset, jotta voit käyttää tätä laitetta rajoituksitta"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Lue lisätietoja koskettamalla."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> ei ole käytössä."</string>
     <string name="conference_call" msgid="3751093130790472426">"Puhelinneuvottelu"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 501627d..4ba4871 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Vous perdrez vos modifications, et la démo recommencera dans <xliff:g id="TIMEOUT">%1$s</xliff:g> secondes…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Annuler"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Réinitialiser maintenant"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Rétablissez la configuration d\'usine de cet appareil pour l\'utiliser sans restrictions"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Touchez ici pour en savoir plus."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Désactivé : <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conférence téléphonique"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index ccff4f3..3dc8b28 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Vous perdrez vos modifications, et la démo recommencera dans <xliff:g id="TIMEOUT">%1$s</xliff:g> secondes…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Annuler"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Réinitialiser maintenant"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Rétablir la configuration d\'usine pour utiliser cet appareil sans restrictions"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Appuyez ici pour en savoir plus."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Élément \"<xliff:g id="LABEL">%1$s</xliff:g>\" désactivé"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conférence téléphonique"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-gl-rES/strings.xml b/core/res/res/values-gl-rES/strings.xml
index 89a9471..077f4e0 100644
--- a/core/res/res/values-gl-rES/strings.xml
+++ b/core/res/res/values-gl-rES/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Perderás os cambios que fixeses e a demostración volverá comezar en <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Restablecer agora"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Restablecemento dos valores de fábrica para usar este dispositivo sen restricións"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toca para acceder a máis información"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Desactivouse <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conferencia telefónica"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-gu-rIN/strings.xml b/core/res/res/values-gu-rIN/strings.xml
index cbb0d9e4a4..149e466 100644
--- a/core/res/res/values-gu-rIN/strings.xml
+++ b/core/res/res/values-gu-rIN/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"તમે કોઈપણ ફેરફારો ગુમાવશો અને ડેમો <xliff:g id="TIMEOUT">%1$s</xliff:g> સેકન્ડમાં ફરી શરૂ થશે…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"રદ કરો"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"હમણાં ફરીથી સેટ કરો"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"આ ઉપકરણનો પ્રતિબંધો વિના ઉપયોગ કરવા માટે ફેક્ટરી રીસેટ કરો"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"વધુ જાણવા માટે ટચ કરો."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> અક્ષમ કર્યું"</string>
     <string name="conference_call" msgid="3751093130790472426">"કોન્ફરન્સ કૉલ"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index e833052..c006df9 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"आपके सभी बदलाव खो जाएंगे और डेमो <xliff:g id="TIMEOUT">%1$s</xliff:g> सेकंड में फिर से शुरू हो जाएगा…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"अभी नहीं"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"अभी रीसेट करें"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"इस डिवाइस को प्रतिबंधों के बिना उपयोग करने के लिए फ़ैक्टरी रीसेट करें"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"अधिक जानने के लिए स्पर्श करें."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"अक्षम <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"कॉन्फ़्रेंस कॉल"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index e811b94..8d90eb92 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -1717,8 +1717,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Sve će se promjene izbrisati, a demonstracija će se ponovo pokrenuti za <xliff:g id="TIMEOUT">%1$s</xliff:g> s…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Odustani"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Vrati na zadano sada"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Uređaj je vraćen na tvorničke postavke da biste ga mogli upotrebljavati bez ograničenja"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Dodirnite da biste saznali više."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> – onemogućeno"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferencijski poziv"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index b0ce160..67e1c0f 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"A módosítások elvesznek, és a bemutató újra elindul <xliff:g id="TIMEOUT">%1$s</xliff:g> másodperc múlva…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Mégse"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Visszaállítás most"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Állítsa vissza a gyári beállításokat az eszköz korlátozások nélküli használata érdekében"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Érintse meg a további információkért."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"A(z) <xliff:g id="LABEL">%1$s</xliff:g> letiltva"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferenciahívás"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-hy-rAM/strings.xml b/core/res/res/values-hy-rAM/strings.xml
index 2ba22af..6444b51 100644
--- a/core/res/res/values-hy-rAM/strings.xml
+++ b/core/res/res/values-hy-rAM/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Կատարված փոփոխությունները չեն պահվի, իսկ ցուցադրական նյութը կրկին կգործարկվի <xliff:g id="TIMEOUT">%1$s</xliff:g> վայրկյանից…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Չեղարկել"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Վերակայել հիմա"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Սարքն առանց սահմանափակումների օգտագործելու համար կատարեք գործարանային վերակայում"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Հպեք՝ ավելին իմանալու համար:"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Անջատած <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Կոնֆերանս զանգ"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 09223cd..873e3ae 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Perubahan yang dibuat akan hilang dan demo akan dimulai lagi dalam <xliff:g id="TIMEOUT">%1$s</xliff:g> detik…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Batal"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Setel ulang sekarang"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Dikembalikan ke setelan pabrik agar perangkat ini dapat digunakan tanpa batasan"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Sentuh untuk mempelajari lebih lanjut."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> dinonaktifkan"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferensi Telepon"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-is-rIS/strings.xml b/core/res/res/values-is-rIS/strings.xml
index 105a647..70a41be 100644
--- a/core/res/res/values-is-rIS/strings.xml
+++ b/core/res/res/values-is-rIS/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Þú glatar öllum breytingum og kynningin byrjar aftur eftir <xliff:g id="TIMEOUT">%1$s</xliff:g> sekúndur…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Hætta við"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Endurstilla núna"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Núllstilltu til að nota þetta tæki án takmarkana"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Snertu til að fá frekari upplýsingar."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Slökkt <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Símafundur"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index 564472e..b3521ff 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Perderai tutte le modifiche e la demo verrà riavviata tra <xliff:g id="TIMEOUT">%1$s</xliff:g> secondi…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Annulla"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Ripristina ora"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Esegui il ripristino dei dati di fabbrica per utilizzare il dispositivo senza limitazioni"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Tocca per ulteriori informazioni."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Widget <xliff:g id="LABEL">%1$s</xliff:g> disattivato"</string>
     <string name="conference_call" msgid="3751093130790472426">"Audioconferenza"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index d4b9bf1..dd41f23 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -1753,8 +1753,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"תאבד את כל השינויים וההדגמה תתחיל שוב בעוד <xliff:g id="TIMEOUT">%1$s</xliff:g> שניות…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"בטל"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"אפס עכשיו"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"איפוס להגדרות היצרן כדי לאפשר שימוש במכשיר ללא מגבלות"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"גע לקבלת מידע נוסף."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> הושבת"</string>
     <string name="conference_call" msgid="3751093130790472426">"שיחת ועידה"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index e2432f1..28db181 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"変更が失われ、<xliff:g id="TIMEOUT">%1$s</xliff:g> 秒後にデモがもう一度開始されます…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"キャンセル"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"今すぐリセット"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"制限なしでこの端末を使用するには初期状態にリセットしてください"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"タップして詳細をご確認ください。"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"停止済みの「<xliff:g id="LABEL">%1$s</xliff:g>」"</string>
     <string name="conference_call" msgid="3751093130790472426">"グループ通話"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ka-rGE/strings.xml b/core/res/res/values-ka-rGE/strings.xml
index 5e069b6..c58803d 100644
--- a/core/res/res/values-ka-rGE/strings.xml
+++ b/core/res/res/values-ka-rGE/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"შეტანილი ცვლილებები დაიკარგება, ხოლო დემონსტრაცია ხელახლა <xliff:g id="TIMEOUT">%1$s</xliff:g> წამში დაიწყება…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"გაუქმება"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ახლავე გადაყენება"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"ამ მოწყობილობის შეზღუდვების გარეშე გამოსაყენებლად, დააბრუნეთ ქარხნული პარამეტრები"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"შეეხეთ მეტის გასაგებად."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"გათიშული <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"საკონფერენციო ზარი"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-kk-rKZ/strings.xml b/core/res/res/values-kk-rKZ/strings.xml
index 729f726..a62cbf0 100644
--- a/core/res/res/values-kk-rKZ/strings.xml
+++ b/core/res/res/values-kk-rKZ/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Барлық өзгеріс жоғалады және демо нұсқасы <xliff:g id="TIMEOUT">%1$s</xliff:g> секундтан кейін қайта қосылады…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Бас тарту"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Қазір бастапқы күйге қайтару"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Осы құрылғыны шектеусіз пайдалану үшін зауыттық параметрлерді қалпына келтіріңіз"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Қосымша мәліметтер алу үшін түртіңіз."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> өшірулі"</string>
     <string name="conference_call" msgid="3751093130790472426">"Конференциялық қоңырау"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-km-rKH/strings.xml b/core/res/res/values-km-rKH/strings.xml
index c3e939e..08cac8c 100644
--- a/core/res/res/values-km-rKH/strings.xml
+++ b/core/res/res/values-km-rKH/strings.xml
@@ -1683,8 +1683,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"អ្នកនឹងបាត់បង់ការផ្លាស់ប្តូរណាមួយ ហើយការបង្ហាញសាកល្បងនឹងចាប់ផ្តើមម្តងទៀតក្នុងរយៈពេល <xliff:g id="TIMEOUT">%1$s</xliff:g> វិនាទីទៀត…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"បោះបង់"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"កំណត់ឡើងវិញឥឡូវនេះ"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"កំណត់ដូចចេញពីរោងចក្រឡើងវិញដើម្បីប្រើឧបករណ៍នេះដោយគ្មានការរឹតបន្តឹង"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"ប៉ះ​ ដើម្បី​​ស្វែងយល់​បន្ថែម។"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> ដែលបានបិទដំណើរការ"</string>
     <string name="conference_call" msgid="3751093130790472426">"ការហៅជាក្រុម"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-kn-rIN/strings.xml b/core/res/res/values-kn-rIN/strings.xml
index 7f80a29..ab9cd90e 100644
--- a/core/res/res/values-kn-rIN/strings.xml
+++ b/core/res/res/values-kn-rIN/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ನೀವು ಯಾವುದೇ ಬದಲಾವಣೆಗಳನ್ನು ಕಳೆದುಕೊಳ್ಳುತ್ತೀರಿ ಮತ್ತು <xliff:g id="TIMEOUT">%1$s</xliff:g> ಸೆಕೆಂಡುಗಳಲ್ಲಿ ಡೆಮೋ ಮತ್ತೆ ಪ್ರಾರಂಭವಾಗುತ್ತದೆ..."</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ರದ್ದುಮಾಡಿ"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ಈಗಲೇ ಮರುಹೊಂದಿಸು"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"ನಿರ್ಬಂಧಗಳು ಇಲ್ಲದೆಯೇ ಈ ಸಾಧನವನ್ನು ಬಳಸಲು ಫ್ಯಾಕ್ಟರಿ ಮರುಹೊಂದಿಸಿ"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"ಇನ್ನಷ್ಟು ತಿಳಿಯಲು ಸ್ಪರ್ಶಿಸಿ."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ"</string>
     <string name="conference_call" msgid="3751093130790472426">"ಕಾನ್ಫರೆನ್ಸ್ ಕರೆ"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index 444c83d..10bb801 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"변경사항이 사라지며 데모가 <xliff:g id="TIMEOUT">%1$s</xliff:g>초 후에 시작됩니다."</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"취소"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"지금 초기화"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"제한 없이 기기를 사용하기 위한 초기화"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"자세한 내용을 보려면 터치하세요."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> 사용 중지됨"</string>
     <string name="conference_call" msgid="3751093130790472426">"다자간 통화"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ky-rKG/strings.xml b/core/res/res/values-ky-rKG/strings.xml
index 2b592bd..0d8a38e 100644
--- a/core/res/res/values-ky-rKG/strings.xml
+++ b/core/res/res/values-ky-rKG/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Бардык өзгөртүүлөр жоголуп, демо режим <xliff:g id="TIMEOUT">%1$s</xliff:g> секунддан кийин кайра башталат…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Жокко чыгаруу"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Баштапкы абалга келтирүү"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Бул түзмөктү чектөөсүз колдонуу үчүн аны баштапкы абалга келтириңиз"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Көбүрөөк билүү үчүн тийип коюңуз."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> өчүрүлдү"</string>
     <string name="conference_call" msgid="3751093130790472426">"Конференц чалуу"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lo-rLA/strings.xml b/core/res/res/values-lo-rLA/strings.xml
index 3a96743..ab1a468 100644
--- a/core/res/res/values-lo-rLA/strings.xml
+++ b/core/res/res/values-lo-rLA/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ທ່ານຈະສູນເສຍການປ່ຽນແປງ ແລະ ເດໂມຈະເລີ່ມອີກຄັ້ງໃນອີກ <xliff:g id="TIMEOUT">%1$s</xliff:g> ວິນາທີ…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ຍົກເລີກ"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ຣີເຊັດດຽວນີ້"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"ຣີເຊັດໃຫ້ເປັນຄ່າໂຮງງານເພື່ອໃຊ້ອຸປະກອນນີ້ໂດຍບໍ່ມີຂໍ້ຈຳກັດ"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"ແຕະເພື່ອສຶກສາເພີ່ມເຕີມ."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"ປິດການນຳໃຊ້ <xliff:g id="LABEL">%1$s</xliff:g> ແລ້ວ"</string>
     <string name="conference_call" msgid="3751093130790472426">"ການປະຊຸມສາຍ"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index d60a010..4557264 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -1753,8 +1753,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Prarasite visus pakeitimus, o demonstracinė versija bus paleista iš naujo po <xliff:g id="TIMEOUT">%1$s</xliff:g> sek…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Atšaukti"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Nustatyti iš naujo dabar"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Atkurkite gamyklinius nustatymus, kad galėtumėte naudoti šį įrenginį be apribojimų"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Palieskite, kad sužinotumėte daugiau."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Išj. valdiklis „<xliff:g id="LABEL">%1$s</xliff:g>“"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferencinis skambutis"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 7afb2ce..0965cf6 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -1717,8 +1717,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Pēc <xliff:g id="TIMEOUT">%1$s</xliff:g> sekundēm zaudēsiet visas izmaiņas un tiks atkārtoti palaista demonstrācija..."</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Atcelt"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Atiestatīt tūlīt"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Rūpnīcas datu atiestatīšana ierīces neierobežotai izmantošanai"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Pieskarieties, lai uzzinātu vairāk."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> atspējots"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferences zvans"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-mk-rMK/strings.xml b/core/res/res/values-mk-rMK/strings.xml
index 139e02a..8d96f5c7 100644
--- a/core/res/res/values-mk-rMK/strings.xml
+++ b/core/res/res/values-mk-rMK/strings.xml
@@ -1683,8 +1683,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Ќе ги изгубите измените и демонстрацијата ќе започне повторно по <xliff:g id="TIMEOUT">%1$s</xliff:g> секунди…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Откажи"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Ресетирај сега"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Ресетирајте до фабричките поставки за уредов да го користите без ограничувања"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Допрете за да дознаете повеќе."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Оневозможен <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Конференциски повик"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ml-rIN/strings.xml b/core/res/res/values-ml-rIN/strings.xml
index fc3eb40..446d4f2 100644
--- a/core/res/res/values-ml-rIN/strings.xml
+++ b/core/res/res/values-ml-rIN/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"മാറ്റങ്ങളെല്ലാം നിങ്ങൾക്ക് നഷ്ടപ്പെടും, <xliff:g id="TIMEOUT">%1$s</xliff:g> സെക്കൻഡിൽ ഡെമോ വീണ്ടും തുടങ്ങും…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"റദ്ദാക്കുക"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ഇപ്പോൾ പുനക്രമീകരിക്കുക"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"നിയന്ത്രണങ്ങൾ ഇല്ലാതെ ഈ ഉപകരണം ഉപയോഗിക്കാൻ ഫാക്ടറി റീസെറ്റ് നടത്തുക"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"കൂടുതലറിയുന്നതിന് സ്‌പർശിക്കുക."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> പ്രവർത്തനരഹിതമാക്കി"</string>
     <string name="conference_call" msgid="3751093130790472426">"കോൺഫറൻസ് കോൾ"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-mn-rMN/strings.xml b/core/res/res/values-mn-rMN/strings.xml
index 2faf163..6199727 100644
--- a/core/res/res/values-mn-rMN/strings.xml
+++ b/core/res/res/values-mn-rMN/strings.xml
@@ -1679,8 +1679,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Таны хийсэн өөрчлөлтийг хадгалахгүй бөгөөд жишээ <xliff:g id="TIMEOUT">%1$s</xliff:g> секундын дотор дахин эхлэх болно..."</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Цуцлах"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Одоо шинэчлэх"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Энэ төхөөрөмжийг хязгаарлалтгүй ашиглахын тулд үйлдвэрийн тохиргоонд дахин тохируулна уу"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Дэлгэрэнгүй үзэх бол дарна уу."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g>-г цуцалсан"</string>
     <string name="conference_call" msgid="3751093130790472426">"Хурлын дуудлага"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-mr-rIN/strings.xml b/core/res/res/values-mr-rIN/strings.xml
index 99465cc..8f2fc0f 100644
--- a/core/res/res/values-mr-rIN/strings.xml
+++ b/core/res/res/values-mr-rIN/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"आपण कोणतेही बदल गमवाल आणि डेमो पुन्हा <xliff:g id="TIMEOUT">%1$s</xliff:g> सेकंदांमध्ये प्रारंभ होईल..."</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"रद्द करा"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"आता रीसेट करा"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"हे डिव्हाइस निर्बंधांशिवाय वापरण्यासाठी फॅक्टरी रीसेट करा"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"अधिक जाणून घेण्यासाठी स्पर्श करा."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> अक्षम केले"</string>
     <string name="conference_call" msgid="3751093130790472426">"परिषद कॉल"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ms-rMY/strings.xml b/core/res/res/values-ms-rMY/strings.xml
index 1e7e281..9b6a20a8 100644
--- a/core/res/res/values-ms-rMY/strings.xml
+++ b/core/res/res/values-ms-rMY/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Anda akan kehilangan sebarang perubahan yang dibuat dan tunjuk cara akan dimulakan sekali lagi dalam masa <xliff:g id="TIMEOUT">%1$s</xliff:g> saat…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Batal"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Tetapkan semula sekarang"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Lakukan tetapan semula kilang untuk menggunakan peranti ini tanpa sekatan"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Ketik untuk mengetahui lebih lanjut."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> dilumpuhkan"</string>
     <string name="conference_call" msgid="3751093130790472426">"Panggilan Sidang"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-my-rMM/strings.xml b/core/res/res/values-my-rMM/strings.xml
index 99d6423..3276dcd 100644
--- a/core/res/res/values-my-rMM/strings.xml
+++ b/core/res/res/values-my-rMM/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ပြောင်းလဲမှုများကို ဆုံးရှုံးသွားမည်ဖြစ်ပြီး သရုပ်ပြချက်သည် <xliff:g id="TIMEOUT">%1$s</xliff:g> စက္ကန့်အတွင်း ပြန်လည်စတင်ပါမည်…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"မလုပ်တော့"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ယခုပြန်လည်သတ်မှတ်ပါ"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"ဤစက်ပစ္စည်းကို ကန့်သတ်ချက်များမပါဘဲ အသုံးပြုရန် စက်ရုံထုတ်ဆက်တင်အတိုင်း ပြန်လည်သတ်မှတ်ပါ"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"ပိုမိုလေ့လာရန် တို့ပါ။"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"ပိတ်ထားသည့် <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"လူအမြောက်အမြားတပြိုင်နက် ခေါ်ဆိုမှု"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 324f0d7..dba56dc 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Du mister eventuelle endringer, og demoen starter på nytt om <xliff:g id="TIMEOUT">%1$s</xliff:g> sekunder."</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Avbryt"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Tilbakestill nå"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Tilbakestill til fabrikkstandard for å bruke denne enheten uten begrensninger"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Trykk for å finne ut mer."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> er slått av"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferansesamtale"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ne-rNP/strings.xml b/core/res/res/values-ne-rNP/strings.xml
index caf3688..27abf1a 100644
--- a/core/res/res/values-ne-rNP/strings.xml
+++ b/core/res/res/values-ne-rNP/strings.xml
@@ -1687,8 +1687,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"तपाईँ सबै परिवर्तनहरू गुमाउनु हुनेछ र <xliff:g id="TIMEOUT">%1$s</xliff:g> सेकेन्डमा डेमो फेरि सुरु हुनेछ…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"रद्द गर्नुहोस्"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"अहिले रिसेट गर्नुहोस्"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"यस यन्त्रलाई सीमितताहरू बिना प्रयोग गर्नका लागि फ्याक्ट्री रिसेट गर्नुहोस्"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"थप जान्नका लागि छुनुहोस्।"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> लाई असक्षम गरियो"</string>
     <string name="conference_call" msgid="3751093130790472426">"सम्मेलन कल"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 0848304..b02c9e1 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -1530,8 +1530,8 @@
     <string name="mediasize_japanese_kahu" msgid="6872696027560065173">"Kahu"</string>
     <string name="mediasize_japanese_kaku2" msgid="2359077233775455405">"Kaku2"</string>
     <string name="mediasize_japanese_you4" msgid="2091777168747058008">"You4"</string>
-    <string name="mediasize_unknown_portrait" msgid="3088043641616409762">"Onbekend portret"</string>
-    <string name="mediasize_unknown_landscape" msgid="4876995327029361552">"Onbekend landschap"</string>
+    <string name="mediasize_unknown_portrait" msgid="3088043641616409762">"Onbekend staand"</string>
+    <string name="mediasize_unknown_landscape" msgid="4876995327029361552">"Onbekend liggend"</string>
     <string name="write_fail_reason_cancelled" msgid="7091258378121627624">"Geannuleerd"</string>
     <string name="write_fail_reason_cannot_write" msgid="8132505417935337724">"Fout bij schrijven van content"</string>
     <string name="reason_unknown" msgid="6048913880184628119">"onbekend"</string>
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Je wijzigingen gaan verloren. De demo wordt opnieuw gestart over <xliff:g id="TIMEOUT">%1$s</xliff:g> seconden…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Annuleren"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Nu resetten"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Zet dit apparaat terug op de fabrieksinstellingen om het zonder beperkingen te gebruiken"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Tik voor meer informatie."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> uitgeschakeld"</string>
     <string name="conference_call" msgid="3751093130790472426">"Telefonische vergadering"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pa-rIN/strings.xml b/core/res/res/values-pa-rIN/strings.xml
index 3e886bc..cb63a54 100644
--- a/core/res/res/values-pa-rIN/strings.xml
+++ b/core/res/res/values-pa-rIN/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ਤੁਸੀਂ ਕਿਸੇ ਵੀ ਤਬਦੀਲੀਆਂ ਨੂੰ ਗੁਆ ਬੈਠੋਂਗੇ ਅਤੇ ਡੈਮੋ <xliff:g id="TIMEOUT">%1$s</xliff:g> ਸਕਿੰਟਾਂ ਵਿੱਚ ਦੁਬਾਰਾ ਚਾਲੂ ਕੀਤਾ ਜਾਵੇਗਾ…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ਰੱਦ ਕਰੋ"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ਹੁਣੇ ਮੁੜ-ਸੈੱਟ ਕਰੋ"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"ਇਸ ਡੀਵਾਈਸ ਨੂੰ ਬਿਨਾਂ ਪਾਬੰਦੀਆਂ ਦੇ ਵਰਤਣ ਲਈ ਫੈਕਟਰੀ ਰੀਸੈੱਟ ਕਰੋ"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"ਹੋਰ ਜਾਣਨ ਲਈ ਸਪਰਸ਼ ਕਰੋ।"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"ਅਯੋਗ ਬਣਾਇਆ ਗਿਆ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"ਕਾਨਫਰੰਸ ਕਾਲ"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 92a2e2a..3cfb242 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -1753,8 +1753,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Stracisz wszystkie wprowadzone zmiany, a tryb demo uruchomi się ponownie za <xliff:g id="TIMEOUT">%1$s</xliff:g> s…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Anuluj"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Resetuj teraz"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Aby używać tego urządzenia bez ograniczeń, przywróć ustawienia fabryczne"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Kliknij, by dowiedzieć się więcej."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Wyłączono: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Połączenie konferencyjne"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index bf9e15d..b5b30a8 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Você perderá todas as alterações. A demonstração será iniciada novamente em <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Reiniciar agora"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Redefinir para a configuração original para usar este dispositivo sem restrições"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toque para saber mais."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Widget <xliff:g id="LABEL">%1$s</xliff:g> desativado"</string>
     <string name="conference_call" msgid="3751093130790472426">"Teleconferência"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index e09daa5..952df71 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Perderá todas as alterações e a demonstração começará novamente dentro de <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Repor agora"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Repor os dados de fábrica para utilizar o dispositivo sem restrições"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toque para saber mais."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> desativado"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conferência"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index bf9e15d..b5b30a8 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Você perderá todas as alterações. A demonstração será iniciada novamente em <xliff:g id="TIMEOUT">%1$s</xliff:g> segundos…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Cancelar"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Reiniciar agora"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Redefinir para a configuração original para usar este dispositivo sem restrições"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Toque para saber mais."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Widget <xliff:g id="LABEL">%1$s</xliff:g> desativado"</string>
     <string name="conference_call" msgid="3751093130790472426">"Teleconferência"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 7f96d2e..6f938d9 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -1717,8 +1717,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Veți pierde toate modificările, iar demonstrația va începe din nou peste <xliff:g id="TIMEOUT">%1$s</xliff:g> secunde…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Anulați"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Resetați acum"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Reveniți la setările din fabrică pentru a folosi acest dispozitiv fără restricții"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Atingeți pentru a afla mai multe."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> a fost dezactivat"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conferință telefonică"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index 9e25bb7..83ae58b 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -1753,8 +1753,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Все изменения будут утеряны. Деморежим будет перезапущен через <xliff:g id="TIMEOUT">%1$s</xliff:g> сек."</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Отмена"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Сбросить"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Сброс до заводских настроек для работы без ограничений"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Нажмите, чтобы узнать больше."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Виджет <xliff:g id="LABEL">%1$s</xliff:g> отключен"</string>
     <string name="conference_call" msgid="3751093130790472426">"Конференц-связь"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-si-rLK/strings.xml b/core/res/res/values-si-rLK/strings.xml
index b106464..2a4ee66 100644
--- a/core/res/res/values-si-rLK/strings.xml
+++ b/core/res/res/values-si-rLK/strings.xml
@@ -1683,8 +1683,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"ඔබට යම් වෙනස් කිරීම් අහිමි වනු ඇති අතර ආදර්ශනය තත්පර <xliff:g id="TIMEOUT">%1$s</xliff:g>කින් නැවත ආරම්භ වනු ඇත…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"අවලංගු කරන්න"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"දැන් යළි සකසන්න"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"සීමා කිරීම්වලින් තොරව මෙම උපාංගය භාවිත කිරීමට කර්මාන්ත ශාලා යළි සැකසීම"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"තව දැන ගැනීමට ස්පර්ශ කරන්න."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"අබල කළ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"සම්මන්ත්‍රණ ඇමතුම"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 50af3cc..ef7678c 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -1753,8 +1753,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Prídete o všetky zmeny a ukážka sa znova spustí o <xliff:g id="TIMEOUT">%1$s</xliff:g> s…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Zrušiť"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Resetovať"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Ak chcete toto zariadenie používať bez obmedzení, obnovte na ňom továrenské nastavenia"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Klepnutím získate ďalšie informácie."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Deaktivovaná miniaplikácia <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferenčný hovor"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 5bc756b..f3a2d23 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -1753,8 +1753,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Morebitne spremembe bodo izgubljene in predstavitev se bo začela znova čez <xliff:g id="TIMEOUT">%1$s</xliff:g> s …"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Prekliči"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Ponastavi"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Ponastavitev naprave na tovarniške nastavitve za uporabo brez omejitev"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Dotaknite se, če želite izvedeti več."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> – onemogočeno"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferenčni klic"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sq-rAL/strings.xml b/core/res/res/values-sq-rAL/strings.xml
index 1095088..69f14d5 100644
--- a/core/res/res/values-sq-rAL/strings.xml
+++ b/core/res/res/values-sq-rAL/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Do të humbasësh çdo ndryshim dhe demonstrimi do të niset përsëri për <xliff:g id="TIMEOUT">%1$s</xliff:g> sekonda…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Anulo"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Rivendos tani"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Rivendos cilësimet e fabrikës për ta përdorur këtë pajisje pa kufizime"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Prek për të mësuar më shumë."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> u çaktivizua"</string>
     <string name="conference_call" msgid="3751093130790472426">"Telefonatë konferencë"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 27d7cad..881421c 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -1717,8 +1717,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Изгубићете све промене и демонстрација ће поново почети за <xliff:g id="TIMEOUT">%1$s</xliff:g> сек…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Откажи"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Ресетуј"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Ресетујте уређај на фабричка подешавања да бисте га користили без ограничења"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Додирните да бисте сазнали више."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Виџет <xliff:g id="LABEL">%1$s</xliff:g> је онемогућен"</string>
     <string name="conference_call" msgid="3751093130790472426">"Конференцијски позив"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index b048762..fdffd46 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Ändringar som du har gjort sparas inte och demon börjar om om <xliff:g id="TIMEOUT">%1$s</xliff:g> sekunder …"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Avbryt"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Återställ nu"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Återställ enheten till standardinställningarna om du vill använda den utan begränsningar"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Tryck här om du vill läsa mer."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> har inaktiverats"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferenssamtal"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 6cfd253..500b33b 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -1679,8 +1679,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Mabadiliko yoyote hayatahifadhiwa. Onyesho litaanza tena baada ya sekunde <xliff:g id="TIMEOUT">%1$s</xliff:g>..."</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Ghairi"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Weka upya sasa"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Rejesha mipangilio iliyotoka nayo kiwandani ili utumie kifaa hiki bila vikwazo"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Gusa ili kupata maelezo zaidi."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> imezimwa"</string>
     <string name="conference_call" msgid="3751093130790472426">"Simu ya Kongamano"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ta-rIN/strings.xml b/core/res/res/values-ta-rIN/strings.xml
index b391912..131d03f 100644
--- a/core/res/res/values-ta-rIN/strings.xml
+++ b/core/res/res/values-ta-rIN/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"மாற்றங்கள் சேமிக்கப்படாது, <xliff:g id="TIMEOUT">%1$s</xliff:g> வினாடிகளில் டெமோ மீண்டும் தொடங்கும்…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ரத்துசெய்"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"இப்போதே மீட்டமை"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"இந்தச் சாதனத்தைக் கட்டுப்பாடுகளின்றிப் பயன்படுத்த, ஆரம்ப நிலைக்கு மீட்டமைக்கவும்"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"மேலும் அறிய தொடவும்."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"முடக்கப்பட்டது: <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"குழு அழைப்பு"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-te-rIN/strings.xml b/core/res/res/values-te-rIN/strings.xml
index 5ec586f..2d6b2ad 100644
--- a/core/res/res/values-te-rIN/strings.xml
+++ b/core/res/res/values-te-rIN/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"మీరు చేసిన ఏవైనా మార్పులను కోల్పోతారు మరియు డెమో <xliff:g id="TIMEOUT">%1$s</xliff:g> సెకన్లలో మళ్లీ ప్రారంభమవుతుంది…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"రద్దు చేయి"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ఇప్పుడే రీసెట్ చేయి"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"ఈ పరికరాన్ని ఎటువంటి పరిమితులు లేకుండా ఉపయోగించడానికి ఫ్యాక్టరీ రీసెట్ చేయండి"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"మరింత తెలుసుకోవడానికి తాకండి."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> నిలిపివేయబడింది"</string>
     <string name="conference_call" msgid="3751093130790472426">"కాన్ఫరెన్స్ కాల్"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 15cbd94..c6a8544 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"การเปลี่ยนแปลงของคุณจะหายไปและการสาธิตจะเริ่มต้นอีกครั้งใน <xliff:g id="TIMEOUT">%1$s</xliff:g> วินาที…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"ยกเลิก"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"รีเซ็ตทันที"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"รีเซ็ตเป็นค่าเริ่มต้นเพื่อใช้อุปกรณ์นี้โดยไร้ข้อจำกัด"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"แตะเพื่อเรียนรู้เพิ่มเติม"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"ปิดใช้ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"การประชุมสาย"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index 31666ee..abcb19a 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Mawawala mo ang anumang mga pagbabago at magsisimulang muli ang demo pagkalipas ng <xliff:g id="TIMEOUT">%1$s</xliff:g> (na) segundo…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Kanselahin"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"I-reset ngayon"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"I-factory reset upang magamit ang device na ito nang walang mga paghihigpit"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Pindutin upang matuto nang higit pa."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Na-disable ang <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Conference Call"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index 0126068..4550784 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Değişiklikleri kaybedeceksiniz ve demo <xliff:g id="TIMEOUT">%1$s</xliff:g> saniye içinde tekrar başlayacak…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"İptal"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Şimdi sıfırla"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Bu cihazı kısıtlama olmadan kullanmak için fabrika ayarlarına sıfırlayın"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Daha fazla bilgi edinmek için dokunun."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> devre dışı"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferans Çağrısı"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index 104badd..b5561b9 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1753,8 +1753,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Ви втратите всі зміни, а демонстрація знову почнеться через <xliff:g id="TIMEOUT">%1$s</xliff:g> с…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Скасувати"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Скинути"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Відновіть заводські параметри, щоб використовувати пристрій без обмежень"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Торкніться, щоб дізнатися більше."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> вимкнено"</string>
     <string name="conference_call" msgid="3751093130790472426">"Конференц-виклик"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-ur-rPK/strings.xml b/core/res/res/values-ur-rPK/strings.xml
index f8224f2..ecb6ee0 100644
--- a/core/res/res/values-ur-rPK/strings.xml
+++ b/core/res/res/values-ur-rPK/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"آپ کی تمام تبدیلیاں ضائع ہو جائیں گی اور ڈیمو <xliff:g id="TIMEOUT">%1$s</xliff:g> سیکنڈز میں دوبارہ شروع ہوگا…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"منسوخ کریں"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"ابھی ری سیٹ کریں"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"بغیر کسی حدود کے استعمال کرنے کیلئے اس آلے کو فیکٹری ری سیٹ کریں"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"مزید جاننے کیلئے ٹچ کریں۔"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"غیر فعال کردہ <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"کانفرنس کال"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-uz-rUZ/strings.xml b/core/res/res/values-uz-rUZ/strings.xml
index 78b5400..dd8b8ba 100644
--- a/core/res/res/values-uz-rUZ/strings.xml
+++ b/core/res/res/values-uz-rUZ/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Har qanday o‘zgarishlar o‘chib ketadi va demo <xliff:g id="TIMEOUT">%1$s</xliff:g> soniyadan so‘ng yana qayta ishga tushadi…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Bekor qilish"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Asl holatga qaytarish"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Bu qurilmadan cheklovlarsiz foydalanish uchun zavod sozlamalarini tiklang"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Ko‘proq o‘rganish uchun bosing."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"<xliff:g id="LABEL">%1$s</xliff:g> vidjeti o‘chirilgan"</string>
     <string name="conference_call" msgid="3751093130790472426">"Konferens-aloqa"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index ca5e3bc..37f1a9b 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Bạn sẽ bị mất mọi thay đổi và bản trình diễn sẽ bắt đầu lại sau <xliff:g id="TIMEOUT">%1$s</xliff:g> giây…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Hủy"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Đặt lại ngay bây giờ"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Khôi phục cài đặt gốc để sử dụng thiết bị này mà không bị hạn chế"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Chạm để tìm hiểu thêm."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"Đã tắt <xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"Cuộc gọi nhiều bên"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index fbe780d..4d210f6 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -114,6 +114,7 @@
         <item name="windowAnimationStyle">@style/Animation.InputMethod</item>
         <item name="imeFullscreenBackground">?colorBackground</item>
         <item name="imeExtractEnterAnimation">@anim/input_method_extract_enter</item>
+        <item name="windowSwipeToDismiss">false</item>
     </style>
 
     <!-- DeviceDefault theme for dialog windows and activities. In contrast to Material, the
diff --git a/core/res/res/values-watch/themes_material.xml b/core/res/res/values-watch/themes_material.xml
index 84bc25f..0cf398b 100644
--- a/core/res/res/values-watch/themes_material.xml
+++ b/core/res/res/values-watch/themes_material.xml
@@ -37,6 +37,7 @@
         <item name="windowAnimationStyle">@style/Animation.InputMethod</item>
         <item name="imeFullscreenBackground">?colorBackground</item>
         <item name="imeExtractEnterAnimation">@anim/input_method_extract_enter</item>
+        <item name="windowSwipeToDismiss">false</item>
     </style>
 
     <!-- Override behaviour to set the theme colours for dialogs, keep them the same. -->
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 2126d12..9def7db 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"您将丢失所有更改,而且演示模式将在 <xliff:g id="TIMEOUT">%1$s</xliff:g> 秒后重新启动…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"取消"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"立即重置"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"恢复出厂设置即可正常使用此设备,不受任何限制"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"触摸即可了解详情。"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"已停用的<xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"电话会议"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index bdd9fe3..6f101ab 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"系統將不會儲存變更,示範將於 <xliff:g id="TIMEOUT">%1$s</xliff:g> 秒後重新開始…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"取消"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"立即重設"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"將此裝置回復至原廠設定後,使用將不受限制"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"輕觸以瞭解詳情。"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"「<xliff:g id="LABEL">%1$s</xliff:g>」已停用"</string>
     <string name="conference_call" msgid="3751093130790472426">"會議通話"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 3c2146b..93acd0e 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"系統不會儲存您所做的變更,示範模式將於 <xliff:g id="TIMEOUT">%1$s</xliff:g> 秒後重新開始…"</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"取消"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"立即重設"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"恢復原廠設定即可正常使用這個裝置"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"輕觸即可瞭解詳情。"</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"已停用的<xliff:g id="LABEL">%1$s</xliff:g>"</string>
     <string name="conference_call" msgid="3751093130790472426">"電話會議"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 011e4f7..14ea738 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -1681,8 +1681,8 @@
     <string name="demo_user_inactivity_timeout_countdown" msgid="5675588824402569506">"Uzolahlekelwa inoma iluphi ushintsho futhi idemo izoqala futhi kumasekhondi angu-<xliff:g id="TIMEOUT">%1$s</xliff:g>..."</string>
     <string name="demo_user_inactivity_timeout_left_button" msgid="5314271347014802475">"Khansela"</string>
     <string name="demo_user_inactivity_timeout_right_button" msgid="5019306703066964808">"Setha kabusha manje"</string>
-    <string name="audit_safemode_notification" msgid="6416076898350685856">"Setha kabusha ukuze usebenzise idivayisi ngaphandle kwemikhawulo"</string>
-    <string name="audit_safemode_notification_details" msgid="1860601176690176413">"Thinta ukuze ufunde kabanzi."</string>
     <string name="suspended_widget_accessibility" msgid="6712143096475264190">"I-<xliff:g id="LABEL">%1$s</xliff:g> ekhutshaziwe"</string>
     <string name="conference_call" msgid="3751093130790472426">"Ikholi yengqungquthela"</string>
+    <!-- no translation found for tooltip_popup_title (8101791425834697618) -->
+    <skip />
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 2d61e6e..95f372c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -582,7 +582,7 @@
         <!-- Image elements -->
         <!-- ============== -->
         <eat-comment />
-i
+
         <!-- Background that can be used behind parts of a UI that provide
              details on data the user is selecting.  For example, this is
              the background element of PreferenceActivity's embedded
@@ -1008,6 +1008,15 @@
         <!-- Background to use for toasts -->
         <attr name="toastFrameBackground" format="reference" />
 
+        <!-- Background to use for tooltip popups -->
+        <attr name="tooltipFrameBackground" format="reference" />
+
+        <!-- Foreground color to use for tooltip popups -->
+        <attr name="tooltipForegroundColor" format="reference|color" />
+
+        <!-- Background color to use for tooltip popups -->
+        <attr name="tooltipBackgroundColor" format="reference|color" />
+
         <!-- Theme to use for Search Dialogs -->
         <attr name="searchDialogTheme" format="reference" />
 
@@ -2865,6 +2874,9 @@
              {@link android.view.View#forceHasOverlappingRendering(boolean)}. -->
         <attr name="forceHasOverlappingRendering" format="boolean" />
 
+        <!-- Defines text displayed in a small popup window on hover or long press. -->
+        <attr name="tooltip" format="string" localization="suggested" />
+
     </declare-styleable>
 
     <!-- Attributes that can be assigned to a tag for a particular View. -->
@@ -4612,6 +4624,23 @@
             screens with limited space for text. -->
             <enum name="full" value="2" />
         </attr>
+        <!-- Specify the type of auto-size. -->
+        <attr name="autoSizeText" format="enum">
+            <!-- No auto-sizing (default). -->
+            <enum name="none" value="0" />
+            <!-- Uniform horizontal and vertical scaling. -->
+            <enum name="xy" value="1" />
+        </attr>
+        <!-- Specify the auto-size step size if <code>autoSizeText</code> is set to
+        <code>xy</code>. The default is 1px. Overwrites
+        <code>autoSizeStepSizeSet</code> if set. -->
+        <attr name="autoSizeStepGranularity" format="dimension" />
+        <!-- Array of dimensions to be used in conjunction with
+        <code>autoSizeText</code> set to <code>xy</code>. Overwrites
+        <code>autoSizeStepGranularity</code> if set. -->
+        <attr name="autoSizeStepSizeSet"/>
+        <!-- The minimum text size constraint to be used when auto-sizing text -->
+        <attr name="autoSizeMinTextSize" format="dimension" />
     </declare-styleable>
     <declare-styleable name="TextViewAppearance">
         <!-- Base text color, typeface, size, and style. -->
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index de86cef..0995bc3 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -186,4 +186,7 @@
 
     <color name="resize_shadow_start_color">#2a000000</color>
     <color name="resize_shadow_end_color">#00000000</color>
+
+    <color name="tooltip_background_dark">#e6616161</color>
+    <color name="tooltip_background_light">#e6FFFFFF</color>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d4119d0..a7c5b2a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -128,6 +128,9 @@
     <integer name="config_activityShortDur">150</integer>
     <integer name="config_activityDefaultDur">220</integer>
 
+    <!-- The duration (in milliseconds) of the tooltip show/hide animations. -->
+    <integer name="config_tooltipAnimTime">150</integer>
+
     <!-- Duration for the dim animation behind a dialog.  This may be either
          a percentage, which is relative to the duration of the enter/open
          animation of the window being shown that is dimming behind, or it may
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 91d7227..5efa55c 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -487,4 +487,19 @@
 
     <!-- Minimum "smallest width" of the display for cascading menus to be enabled. -->
     <dimen name="cascading_menus_min_smallest_width">720dp</dimen>
+
+    <!-- Tooltip dimensions. -->
+    <!-- Vertical offset from the edge of the anchor view. -->
+    <dimen name="tooltip_y_offset">20dp</dimen>
+    <!-- Height of the tooltip. -->
+    <dimen name="tooltip_height">32dp</dimen>
+    <!-- The tooltip does not get closer than this to the window edge -->
+    <dimen name="tooltip_margin">8dp</dimen>
+    <!-- Left/right padding of the tooltip text. -->
+    <dimen name="tooltip_horizontal_padding">16dp</dimen>
+    <!-- Border corner radius of the tooltip window. -->
+    <dimen name="tooltip_corner_radius">2dp</dimen>
+    <!-- View with the height equal or above this threshold will have a tooltip anchored
+    to the mouse/touch position -->
+    <dimen name="tooltip_precise_anchor_threshold">96dp</dimen>
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index ed68582..200961f 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2759,6 +2759,11 @@
         <public name="fontStyle" />
         <public name="font" />
         <public name="fontWeight" />
+        <public name="tooltip" />
+        <public name="autoSizeText" />
+        <public name="autoSizeStepGranularity" />
+        <public name="autoSizeStepSizeSet" />
+        <public name="autoSizeMinTextSize" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index d42ec90..4070d48 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -4440,14 +4440,12 @@
     <!-- Text of button to allow user to abort countdown and immediately start another session in retail demo mode [CHAR LIMIT=40] -->
     <string name="demo_user_inactivity_timeout_right_button">Reset now</string>
 
-    <!-- Title of notification shown when device has been forced to safe mode after a security compromise. -->
-    <string name="audit_safemode_notification">Factory reset to use this device without restrictions</string>
-    <!-- Description of notification shown when device has been forced to safe mode after a security compromise. -->
-    <string name="audit_safemode_notification_details">Touch to learn more.</string>
-
     <!-- Accessibilty string added to a widget that has been suspended [CHAR LIMIT=20] -->
     <string name="suspended_widget_accessibility">Disabled <xliff:g id="label" example="Calendar">%1$s</xliff:g></string>
 
     <!-- Label used by Telephony code, assigned as the display name for conference calls [CHAR LIMIT=60] -->
     <string name="conference_call">Conference Call</string>
+
+    <!-- Title for a tooltip popup window [CHAR LIMIT=NONE] -->
+    <string name="tooltip_popup_title">Tooltip Popup</string>
 </resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 937428b..0f756b9 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -159,6 +159,11 @@
         <item name="windowExitAnimation">@anim/toast_exit</item>
     </style>
 
+    <style name="Animation.Tooltip">
+        <item name="windowEnterAnimation">@anim/tooltip_enter</item>
+        <item name="windowExitAnimation">@anim/tooltip_exit</item>
+    </style>
+
     <style name="Animation.DropDownDown">
         <item name="windowEnterAnimation">@anim/grow_fade_in</item>
         <item name="windowExitAnimation">@anim/shrink_fade_out</item>
@@ -950,6 +955,11 @@
         <item name="fontFamily">sans-serif-condensed</item>
     </style>
 
+    <style name="TextAppearance.Tooltip">
+        <item name="fontFamily">sans-serif</item>
+        <item name="textSize">14sp</item>
+    </style>
+
     <style name="Widget.ActivityChooserView">
         <item name="gravity">center</item>
         <item name="background">@drawable/ab_share_pack_holo_dark</item>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c719664..a28a6fd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -435,6 +435,8 @@
   <java-symbol type="dimen" name="search_view_preferred_height" />
   <java-symbol type="dimen" name="textview_error_popup_default_width" />
   <java-symbol type="dimen" name="toast_y_offset" />
+  <java-symbol type="dimen" name="tooltip_precise_anchor_threshold" />
+  <java-symbol type="dimen" name="tooltip_y_offset" />
   <java-symbol type="dimen" name="action_bar_stacked_max_height" />
   <java-symbol type="dimen" name="action_bar_stacked_tab_max_width" />
   <java-symbol type="dimen" name="notification_text_size" />
@@ -1123,6 +1125,7 @@
   <java-symbol type="string" name="demo_starting_message" />
   <java-symbol type="string" name="demo_restarting_message" />
   <java-symbol type="string" name="conference_call" />
+  <java-symbol type="string" name="tooltip_popup_title" />
 
 
   <java-symbol type="plurals" name="bugreport_countdown" />
@@ -1373,6 +1376,7 @@
   <java-symbol type="layout" name="textview_hint" />
   <java-symbol type="layout" name="time_picker_legacy" />
   <java-symbol type="layout" name="time_picker_dialog" />
+  <java-symbol type="layout" name="tooltip" />
   <java-symbol type="layout" name="transient_notification" />
   <java-symbol type="layout" name="voice_interaction_session" />
   <java-symbol type="layout" name="web_text_view_dropdown" />
@@ -1423,6 +1427,7 @@
   <java-symbol type="style" name="Animation.DropDownUp" />
   <java-symbol type="style" name="Animation.DropDownDown" />
   <java-symbol type="style" name="Animation.PopupWindow" />
+  <java-symbol type="style" name="Animation.Tooltip" />
   <java-symbol type="style" name="Animation.TypingFilter" />
   <java-symbol type="style" name="Animation.TypingFilterRestore" />
   <java-symbol type="style" name="Animation.Dream" />
@@ -1915,8 +1920,6 @@
   <java-symbol type="string" name="config_customVpnConfirmDialogComponent" />
   <java-symbol type="string" name="config_defaultNetworkScorerPackageName" />
   <java-symbol type="string" name="config_persistentDataPackageName" />
-  <java-symbol type="string" name="audit_safemode_notification" />
-  <java-symbol type="string" name="audit_safemode_notification_details" />
   <java-symbol type="string" name="reset_retail_demo_mode_title" />
   <java-symbol type="string" name="reset_retail_demo_mode_text" />
   <java-symbol type="string" name="demo_user_inactivity_timeout_title" />
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 5b2522f..357eb4b 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -439,6 +439,11 @@
         <item name="lightRadius">@dimen/light_radius</item>
         <item name="ambientShadowAlpha">@dimen/ambient_shadow_alpha</item>
         <item name="spotShadowAlpha">@dimen/spot_shadow_alpha</item>
+
+        <!-- Tooltip popup properties -->
+        <item name="tooltipFrameBackground">@drawable/tooltip_frame</item>
+        <item name="tooltipForegroundColor">@color/bright_foreground_light</item>
+        <item name="tooltipBackgroundColor">@color/tooltip_background_light</item>
     </style>
 
     <!-- Variant of {@link #Theme} with no title bar -->
@@ -553,6 +558,10 @@
         <item name="floatingToolbarItemBackgroundDrawable">@drawable/item_background_material_light</item>
         <item name="floatingToolbarOpenDrawable">@drawable/ic_menu_moreoverflow_material_light</item>
         <item name="floatingToolbarPopupBackgroundDrawable">@drawable/floating_popup_background_light</item>
+
+        <!-- Tooltip popup colors -->
+        <item name="tooltipForegroundColor">@color/bright_foreground_dark</item>
+        <item name="tooltipBackgroundColor">@color/tooltip_background_dark</item>
     </style>
 
     <!-- Variant of {@link #Theme_Light} with no title bar -->
diff --git a/core/res/res/values/themes_material.xml b/core/res/res/values/themes_material.xml
index 0de773b..92bb3ea 100644
--- a/core/res/res/values/themes_material.xml
+++ b/core/res/res/values/themes_material.xml
@@ -398,6 +398,10 @@
         <item name="colorControlHighlight">@color/ripple_material_dark</item>
         <item name="colorButtonNormal">@color/btn_default_material_dark</item>
         <item name="colorSwitchThumbNormal">@color/switch_thumb_material_dark</item>
+
+        <!-- Tooltip popup properties -->
+        <item name="tooltipForegroundColor">@color/foreground_material_light</item>
+        <item name="tooltipBackgroundColor">@color/tooltip_background_light</item>
     </style>
 
     <!-- Material theme (light version). -->
@@ -762,6 +766,10 @@
         <item name="colorControlHighlight">@color/ripple_material_light</item>
         <item name="colorButtonNormal">@color/btn_default_material_light</item>
         <item name="colorSwitchThumbNormal">@color/switch_thumb_material_light</item>
+
+        <!-- Tooltip popup properties -->
+        <item name="tooltipForegroundColor">@color/foreground_material_dark</item>
+        <item name="tooltipBackgroundColor">@color/tooltip_background_dark</item>
     </style>
 
     <!-- Variant of the material (light) theme that has a solid (opaque) action bar
diff --git a/core/tests/coretests/src/android/net/RoughtimeClientTest.java b/core/tests/coretests/src/android/net/RoughtimeClientTest.java
deleted file mode 100644
index cd26804..0000000
--- a/core/tests/coretests/src/android/net/RoughtimeClientTest.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net;
-
-import android.net.RoughtimeClient;
-import android.util.Log;
-import libcore.util.HexEncoding;
-
-import java.io.IOException;
-import java.net.DatagramPacket;
-import java.net.DatagramSocket;
-import java.net.InetAddress;
-import java.net.SocketException;
-import java.security.MessageDigest;
-import java.util.Arrays;
-import junit.framework.TestCase;
-
-
-public class RoughtimeClientTest extends TestCase {
-    private static final String TAG = "RoughtimeClientTest";
-
-    private static final long TEST_TIME = 8675309;
-    private static final int TEST_RADIUS = 42;
-
-    private final RoughtimeTestServer mServer = new RoughtimeTestServer();
-    private final RoughtimeClient mClient = new RoughtimeClient();
-
-    public void testBasicWorkingRoughtimeClientQuery() throws Exception {
-        mServer.shouldRespond(true);
-        assertTrue(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500));
-        assertEquals(1, mServer.numRequestsReceived());
-        assertEquals(1, mServer.numRepliesSent());
-    }
-
-    public void testDnsResolutionFailure() throws Exception {
-        mServer.shouldRespond(true);
-        assertFalse(mClient.requestTime("roughtime.server.doesnotexist.example", 5000));
-    }
-
-    public void testTimeoutFailure() throws Exception {
-        mServer.shouldRespond(false);
-        assertFalse(mClient.requestTime(mServer.getAddress(), mServer.getPort(), 500));
-        assertEquals(1, mServer.numRequestsReceived());
-        assertEquals(0, mServer.numRepliesSent());
-    }
-
-    private static MessageDigest md = null;
-
-    private static byte[] signedResponse(byte[] nonce) {
-        RoughtimeClient.Message signed = new RoughtimeClient.Message();
-
-        try {
-            if (md == null) {
-                md = MessageDigest.getInstance("SHA-512");
-            }
-        } catch(Exception e) {
-            return null;
-        }
-
-        md.update(new byte[]{0});
-        byte[] hash = md.digest(nonce);
-        signed.put(RoughtimeClient.Tag.ROOT, hash);
-        signed.putLong(RoughtimeClient.Tag.MIDP, TEST_TIME);
-        signed.putInt(RoughtimeClient.Tag.RADI, TEST_RADIUS);
-
-        return signed.serialize();
-    }
-
-    private static byte[] response(byte[] nonce) {
-        RoughtimeClient.Message msg = new RoughtimeClient.Message();
-
-        msg.put(RoughtimeClient.Tag.SREP, signedResponse(nonce));
-        msg.putInt(RoughtimeClient.Tag.INDX, 0);
-        msg.put(RoughtimeClient.Tag.PATH, new byte[0]);
-
-        return msg.serialize();
-    }
-
-    private static class RoughtimeTestServer {
-        private final Object mLock = new Object();
-        private final DatagramSocket mSocket;
-        private final InetAddress mAddress;
-        private final int mPort;
-        private int mRcvd;
-        private int mSent;
-        private Thread mListeningThread;
-        private boolean mShouldRespond = true;
-
-        public RoughtimeTestServer() {
-            mSocket = makeSocket();
-            mAddress = mSocket.getLocalAddress();
-            mPort = mSocket.getLocalPort();
-            Log.d(TAG, "testing server listening on (" + mAddress + ", " + mPort + ")");
-
-            mListeningThread = new Thread() {
-                public void run() {
-                    while (true) {
-                        byte[] buffer = new byte[2048];
-                        DatagramPacket request = new DatagramPacket(buffer, buffer.length);
-                        try {
-                            mSocket.receive(request);
-                        } catch (IOException e) {
-                            Log.e(TAG, "datagram receive error: " + e);
-                            break;
-                        }
-                        synchronized (mLock) {
-                            mRcvd++;
-
-                            if (! mShouldRespond) {
-                                continue;
-                            }
-
-                            RoughtimeClient.Message msg =
-                                RoughtimeClient.Message.deserialize(
-                                    Arrays.copyOf(buffer, request.getLength()));
-
-                            byte[] nonce = msg.get(RoughtimeClient.Tag.NONC);
-                            if (nonce.length != 64) {
-                                Log.e(TAG, "Nonce is wrong length.");
-                            }
-
-                            try {
-                                request.setData(response(nonce));
-                                mSocket.send(request);
-                            } catch (IOException e) {
-                                Log.e(TAG, "datagram send error: " + e);
-                                break;
-                            }
-                            mSent++;
-                        }
-                    }
-                    mSocket.close();
-                }
-            };
-            mListeningThread.start();
-        }
-
-        private DatagramSocket makeSocket() {
-            DatagramSocket socket;
-            try {
-                socket = new DatagramSocket(0, InetAddress.getLoopbackAddress());
-            } catch (SocketException e) {
-                Log.e(TAG, "Failed to create test server socket: " + e);
-                return null;
-            }
-            return socket;
-        }
-
-        public void shouldRespond(boolean value) { mShouldRespond = value; }
-
-        public InetAddress getAddress() { return mAddress; }
-        public int getPort() { return mPort; }
-        public int numRequestsReceived() { synchronized (mLock) { return mRcvd; } }
-        public int numRepliesSent() { synchronized (mLock) { return mSent; } }
-    }
-}
diff --git a/core/tests/coretests/src/android/provider/SettingsProviderTest.java b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
index e6d3158..0a32e43 100644
--- a/core/tests/coretests/src/android/provider/SettingsProviderTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsProviderTest.java
@@ -349,6 +349,7 @@
         assertCanBeHandled(new Intent(Settings.ACTION_USER_DICTIONARY_SETTINGS));
         assertCanBeHandled(new Intent(Settings.ACTION_WIFI_IP_SETTINGS));
         assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SETTINGS));
+        assertCanBeHandled(new Intent(Settings.ACTION_WIFI_SAVED_NETWORK_SETTINGS));
         assertCanBeHandled(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
     }
 
diff --git a/docs/html/reference/images/graphics/colorspace_aces.png b/docs/html/reference/images/graphics/colorspace_aces.png
new file mode 100644
index 0000000..efafe5c
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_aces.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_acescg.png b/docs/html/reference/images/graphics/colorspace_acescg.png
new file mode 100644
index 0000000..55f6ab5
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_acescg.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_adobe_rgb.png b/docs/html/reference/images/graphics/colorspace_adobe_rgb.png
new file mode 100644
index 0000000..cb7d602
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_adobe_rgb.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_bt2020.png b/docs/html/reference/images/graphics/colorspace_bt2020.png
new file mode 100644
index 0000000..34a3853
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_bt2020.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_bt709.png b/docs/html/reference/images/graphics/colorspace_bt709.png
new file mode 100644
index 0000000..ba637f5
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_bt709.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_dci_p3.png b/docs/html/reference/images/graphics/colorspace_dci_p3.png
new file mode 100644
index 0000000..19144e7
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_dci_p3.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_display_p3.png b/docs/html/reference/images/graphics/colorspace_display_p3.png
new file mode 100644
index 0000000..a86c60a
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_display_p3.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_ntsc_1953.png b/docs/html/reference/images/graphics/colorspace_ntsc_1953.png
new file mode 100644
index 0000000..bce93da
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_ntsc_1953.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_pro_photo_rgb.png b/docs/html/reference/images/graphics/colorspace_pro_photo_rgb.png
new file mode 100644
index 0000000..74c95be
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_pro_photo_rgb.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_scrgb.png b/docs/html/reference/images/graphics/colorspace_scrgb.png
new file mode 100644
index 0000000..2351b8e
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_scrgb.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_smpte_c.png b/docs/html/reference/images/graphics/colorspace_smpte_c.png
new file mode 100644
index 0000000..360bb73
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_smpte_c.png
Binary files differ
diff --git a/docs/html/reference/images/graphics/colorspace_srgb.png b/docs/html/reference/images/graphics/colorspace_srgb.png
new file mode 100644
index 0000000..ba637f5
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_srgb.png
Binary files differ
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index e628cf8..756087c 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -463,12 +463,18 @@
          * This configuration is very flexible and offers the best
          * quality. It should be used whenever possible.
          */
-        ARGB_8888   (5);
+        ARGB_8888   (5),
+
+
+        /**
+          * @hide
+         */
+        HARDWARE    (6);
 
         final int nativeInt;
 
         private static Config sConfigs[] = {
-            null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888
+            null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, HARDWARE
         };
 
         Config(int ni) {
diff --git a/graphics/java/android/graphics/Color.java b/graphics/java/android/graphics/Color.java
index c627297..a2c104a 100644
--- a/graphics/java/android/graphics/Color.java
+++ b/graphics/java/android/graphics/Color.java
@@ -18,11 +18,12 @@
 
 import android.annotation.ColorInt;
 import android.annotation.Size;
-import android.util.MathUtils;
+
 import com.android.internal.util.XmlUtils;
 
 import java.util.HashMap;
 import java.util.Locale;
+import java.util.function.DoubleUnaryOperator;
 
 /**
  * The Color class defines methods for creating and converting color ints.
@@ -94,7 +95,7 @@
      */
     @ColorInt
     public static int rgb(int red, int green, int blue) {
-        return (0xFF << 24) | (red << 16) | (green << 8) | blue;
+        return 0xff000000 | (red << 16) | (green << 8) | blue;
     }
 
     /**
@@ -121,12 +122,11 @@
      * @return a value between 0 (darkest black) and 1 (lightest white)
      */
     public static float luminance(@ColorInt int color) {
-        double red = Color.red(color) / 255.0;
-        red = red < 0.03928 ? red / 12.92 : Math.pow((red + 0.055) / 1.055, 2.4);
-        double green = Color.green(color) / 255.0;
-        green = green < 0.03928 ? green / 12.92 : Math.pow((green + 0.055) / 1.055, 2.4);
-        double blue = Color.blue(color) / 255.0;
-        blue = blue < 0.03928 ? blue / 12.92 : Math.pow((blue + 0.055) / 1.055, 2.4);
+        ColorSpace.Rgb cs = (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.SRGB);
+        DoubleUnaryOperator eotf = cs.getEotf();
+        double red = eotf.applyAsDouble(Color.red(color) / 255.0);
+        double green = eotf.applyAsDouble(Color.green(color) / 255.0);
+        double blue = eotf.applyAsDouble(Color.blue(color) / 255.0);
         return (float) ((0.2126 * red) + (0.7152 * green) + (0.0722 * blue));
     }
 
@@ -250,9 +250,8 @@
     }
 
     private static final HashMap<String, Integer> sColorNameMap;
-
     static {
-        sColorNameMap = new HashMap<String, Integer>();
+        sColorNameMap = new HashMap<>();
         sColorNameMap.put("black", BLACK);
         sColorNameMap.put("darkgray", DKGRAY);
         sColorNameMap.put("gray", GRAY);
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
new file mode 100644
index 0000000..4f2465f
--- /dev/null
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -0,0 +1,3116 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Size;
+import android.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.function.DoubleUnaryOperator;
+
+/**
+ * {@usesMathJax}
+ *
+ * <p>A {@link ColorSpace} is used to identify a specific organization of colors.
+ * Each color space is characterized by a {@link Model color model} that defines
+ * how a color value is represented (for instance the {@link Model#RGB RGB} color
+ * model defines a color value as a triplet of numbers).</p>
+ *
+ * <p>Each component of a color must fall within a valid range, specific to each
+ * color space, defined by {@link #getMinValue(int)} and {@link #getMaxValue(int)}
+ * This range is commonly \([0..1]\). While it is recommended to use values in the
+ * valid range, a color space always clamps input and output values when performing
+ * operations such as converting to a different color space.</p>
+ *
+ * <h3>Using color spaces</h3>
+ *
+ * <p>This implementation provides a pre-defined set of common color spaces
+ * described in the {@link Named} enum. To obtain an instance of one of the
+ * pre-defined color spaces, simply invoke {@link #get(Named)}:</p>
+ *
+ * <pre class="prettyprint">
+ * ColorSpace sRgb = ColorSpace.get(ColorSpace.Named.SRGB);
+ * </pre>
+ *
+ * <p>The {@link #get(Named)} method always returns the same instance for a given
+ * name. Color spaces with an {@link Model#RGB RGB} color model can be safely
+ * cast to {@link Rgb}. Doing so gives you access to more APIs to query various
+ * properties of RGB color models: color gamut primaries, transfer functions,
+ * conversions to and from linear space, etc. Please refer to {@link Rgb} for
+ * more information.</p>
+ *
+ * <p>The documentation of {@link Named} provides a detailed description of the
+ * various characteristics of each available color space.</p>
+ *
+ * <h3>Color space conversions</h3>
+
+ * <p>To allow conversion between color spaces, this implementation uses the CIE
+ * XYZ profile connection space (PCS). Color values can be converted to and from
+ * this PCS using {@link #toXyz(float[])} and {@link #fromXyz(float[])}.</p>
+ *
+ * <p>For color space with a non-RGB color model, the white point of the PCS
+ * <em>must be</em> the CIE standard illuminant D50. RGB color spaces use their
+ * native white point (D65 for {@link Named#SRGB sRGB} for instance and must
+ * undergo {@link Adaptation chromatic adaptation} as necessary.</p>
+ *
+ * <p>Since the white point of the PCS is not defined for RGB color space, it is
+ * highly recommended to use the variants of the {@link #connect(ColorSpace, ColorSpace)}
+ * method to perform conversions between color spaces. A color space can be
+ * manually adapted to a specific white point using {@link #adapt(ColorSpace, float[])}.
+ * Please refer to the documentation of {@link Rgb RGB color spaces} for more
+ * information. Several common CIE standard illuminants are provided in this
+ * class as reference (see {@link #ILLUMINANT_D65} or {@link #ILLUMINANT_D50}
+ * for instance).</p>
+ *
+ * <p>Here is an example of how to convert from a color space to another:</p>
+ *
+ * <pre class="prettyprint">
+ * // Convert from DCI-P3 to Rec.2020
+ * ColorSpace.Connector connector = ColorSpace.connect(
+ *         ColorSpace.get(ColorSpace.Named.DCI_P3),
+ *         ColorSpace.get(ColorSpace.Named.BT2020));
+ *
+ * float[] bt2020 = connector.transform(p3r, p3g, p3b);
+ * </pre>
+ *
+ * <p>You can easily convert to {@link Named#SRGB sRGB} by omitting the second
+ * parameter:</p>
+ *
+ * <pre class="prettyprint">
+ * // Convert from DCI-P3 to sRGB
+ * ColorSpace.Connector connector = ColorSpace.connect(ColorSpace.get(ColorSpace.Named.DCI_P3));
+ *
+ * float[] sRGB = connector.transform(p3r, p3g, p3b);
+ * </pre>
+ *
+ * <p>Conversions also work between color spaces with different color models:</p>
+ *
+ * <pre class="prettyprint">
+ * // Convert from CIE L*a*b* (color model Lab) to Rec.709 (color model RGB)
+ * ColorSpace.Connector connector = ColorSpace.connect(
+ *         ColorSpace.get(ColorSpace.Named.CIE_LAB),
+ *         ColorSpace.get(ColorSpace.Named.BT709));
+ * </pre>
+ *
+ * <h3>Color spaces and multi-threading</h3>
+ *
+ * <p>Color spaces and other related classes ({@link Connector} for instance)
+ * are immutable and stateless. They can be safely used from multiple concurrent
+ * threads.</p>
+ *
+ * <p>Public static methods provided by this class, such as {@link #get(Named)}
+ * and {@link #connect(ColorSpace, ColorSpace)}, are also guaranteed to be
+ * thread-safe.</p>
+ *
+ * @see #get(Named)
+ * @see Named
+ * @see Model
+ * @see Connector
+ * @see Adaptation
+ */
+@SuppressWarnings("StaticInitializerReferencesSubClass")
+public abstract class ColorSpace {
+    /**
+     * Standard CIE 1931 2° illuminant A, encoded in xyY.
+     * This illuminant has a color temperature of 2856K.
+     */
+    public static final float[] ILLUMINANT_A   = { 0.44757f, 0.40745f };
+    /**
+     * Standard CIE 1931 2° illuminant B, encoded in xyY.
+     * This illuminant has a color temperature of 4874K.
+     */
+    public static final float[] ILLUMINANT_B   = { 0.34842f, 0.35161f };
+    /**
+     * Standard CIE 1931 2° illuminant C, encoded in xyY.
+     * This illuminant has a color temperature of 6774K.
+     */
+    public static final float[] ILLUMINANT_C   = { 0.31006f, 0.31616f };
+    /**
+     * Standard CIE 1931 2° illuminant D50, encoded in xyY.
+     * This illuminant has a color temperature of 5003K. This illuminant
+     * is used by the profile connection space in ICC profiles.
+     */
+    public static final float[] ILLUMINANT_D50 = { 0.34567f, 0.35850f };
+    /**
+     * Standard CIE 1931 2° illuminant D55, encoded in xyY.
+     * This illuminant has a color temperature of 5503K.
+     */
+    public static final float[] ILLUMINANT_D55 = { 0.33242f, 0.34743f };
+    /**
+     * Standard CIE 1931 2° illuminant D60, encoded in xyY.
+     * This illuminant has a color temperature of 6004K.
+     */
+    public static final float[] ILLUMINANT_D60 = { 0.32168f, 0.33767f };
+    /**
+     * Standard CIE 1931 2° illuminant D65, encoded in xyY.
+     * This illuminant has a color temperature of 6504K. This illuminant
+     * is commonly used in RGB color spaces such as sRGB, BT.209, etc.
+     */
+    public static final float[] ILLUMINANT_D65 = { 0.31271f, 0.32902f };
+    /**
+     * Standard CIE 1931 2° illuminant D75, encoded in xyY.
+     * This illuminant has a color temperature of 7504K.
+     */
+    public static final float[] ILLUMINANT_D75 = { 0.29902f, 0.31485f };
+    /**
+     * Standard CIE 1931 2° illuminant E, encoded in xyY.
+     * This illuminant has a color temperature of 5454K.
+     */
+    public static final float[] ILLUMINANT_E   = { 0.33333f, 0.33333f };
+
+    /**
+     * The minimum ID value a color space can have.
+     *
+     * @see #getId()
+     */
+    public static final int MIN_ID = -1; // Do not change
+    /**
+     * The maximum ID value a color space can have.
+     *
+     * @see #getId()
+     */
+    public static final int MAX_ID = 64; // Do not change, used to encode in longs
+
+    private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
+    private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
+    private static final float[] ILLUMINANT_D50_XYZ = { 0.964212f, 1.0f, 0.825188f };
+
+    // See static initialization block next to #get(Named)
+    private static final ColorSpace[] sNamedColorSpaces = new ColorSpace[Named.values().length];
+
+    @NonNull private final String mName;
+    @NonNull private final Model mModel;
+    @IntRange(from = MIN_ID, to = MAX_ID) private final int mId;
+
+    /**
+     * {@usesMathJax}
+     *
+     * <p>List of common, named color spaces. A corresponding instance of
+     * {@link ColorSpace} can be obtained by calling {@link ColorSpace#get(Named)}:</p>
+     *
+     * <pre class="prettyprint">
+     * ColorSpace cs = ColorSpace.get(ColorSpace.Named.DCI_P3);
+     * </pre>
+     *
+     * <p>The properties of each color space are described below (see {@link #SRGB sRGB}
+     * for instance). When applicable, the color gamut of each color space is compared
+     * to the color gamut of sRGB using a CIE 1931 xy chromaticity diagram. This diagram
+     * shows the location of the color space's primaries and white point.</p>
+     *
+     * @see ColorSpace#get(Named)
+     */
+    public enum Named {
+        // NOTE: Do NOT change the order of the enum
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \le 0.0031308 \\
+         *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \gt 0.0031308 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \le 0.04045 \\
+         *             \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \gt 0.04045 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
+         *     <figcaption style="text-align: center;">sRGB</figcaption>
+         * </p>
+         */
+        SRGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space sRGB standardized as IEC 61966-2.1:1999.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">sRGB IEC61966-2.1 (Linear)</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(C_{sRGB} = C_{linear}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(C_{linear} = C_{sRGB}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
+         *     <figcaption style="text-align: center;">sRGB</figcaption>
+         * </p>
+         */
+        LINEAR_SRGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space scRGB-nl standardized as IEC 61966-2-2:2003.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">scRGB-nl IEC 61966-2-2:2003</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{scRGB} = \begin{cases} sign(C_{linear}) 12.92 \times \left| C_{linear} \right| &
+         *                      \left| C_{linear} \right| \le 0.0031308 \\
+         *             sign(C_{linear}) 1.055 \times \left| C_{linear} \right| ^{\frac{1}{2.4}} - 0.055 &
+         *                      \left| C_{linear} \right| \gt 0.0031308 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}sign(C_{scRGB}) \frac{\left| C_{scRGB} \right|}{12.92} &
+         *                  \left| C_{scRGB} \right| \le 0.04045 \\
+         *             sign(C_{scRGB}) \left( \frac{\left| C_{scRGB} \right| + 0.055}{1.055} \right) ^{2.4} &
+         *                  \left| C_{scRGB} \right| \gt 0.04045 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([-0.5..7.5[\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
+         *     <figcaption style="text-align: center;">Extended RGB (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        EXTENDED_SRGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space scRGB standardized as IEC 61966-2-2:2003.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">scRGB IEC 61966-2-2:2003</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(C_{scRGB} = C_{linear}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(C_{linear} = C_{scRGB}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([-0.5..7.5[\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
+         *     <figcaption style="text-align: center;">Extended RGB (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        LINEAR_EXTENDED_SRGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space BT.709 standardized as Rec. ITU-R BT.709-5.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.640</td><td>0.300</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.330</td><td>0.600</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.709-5</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
+         *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
+         *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_bt709.png" />
+         *     <figcaption style="text-align: center;">BT.709</figcaption>
+         * </p>
+         */
+        BT709,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space BT.2020 standardized as Rec. ITU-R BT.2020-1.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.708</td><td>0.170</td><td>0.131</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.292</td><td>0.797</td><td>0.046</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Rec. ITU-R BT.2020-1</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{BT2020} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.0181 \\
+         *             1.0993 \times C_{linear}^{\frac{1}{2.2}} - 0.0993 & C_{linear} \ge 0.0181 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{BT2020}}{4.5} & C_{BT2020} \lt 0.08145 \\
+         *             \left( \frac{C_{BT2020} + 0.0993}{1.0993} \right) ^{2.2} & C_{BT2020} \ge 0.08145 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_bt2020.png" />
+         *     <figcaption style="text-align: center;">BT.2020 (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        BT2020,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space DCI-P3 standardized as SMPTE RP 431-2-2007.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.314</td></tr>
+         *     <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.351</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">SMPTE RP 431-2-2007 DCI (P3)</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">N/A</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(C_{P3} = C_{linear}^{\frac{1}{2.6}}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(C_{linear} = C_{P3}^{2.6}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_dci_p3.png" />
+         *     <figcaption style="text-align: center;">DCI-P3 (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        DCI_P3,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space Display P3 based on SMPTE RP 431-2-2007 and IEC 61966-2.1:1999.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.680</td><td>0.265</td><td>0.150</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.320</td><td>0.690</td><td>0.060</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Display P3</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{sRGB} = \begin{cases} 12.92 \times C_{linear} & C_{linear} \le 0.0031308 \\
+         *             1.055 \times C_{linear}^{\frac{1}{2.4}} - 0.055 & C_{linear} \gt 0.0031308 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{sRGB}}{12.92} & C_{sRGB} \le 0.04045 \\
+         *             \left( \frac{C_{sRGB} + 0.055}{1.055} \right) ^{2.4} & C_{sRGB} \gt 0.04045 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_display_p3.png" />
+         *     <figcaption style="text-align: center;">Display P3 (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        DISPLAY_P3,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space NTSC, 1953 standard.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.67</td><td>0.21</td><td>0.14</td><td>0.310</td></tr>
+         *     <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.08</td><td>0.316</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">NTSC (1953)</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">C</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
+         *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
+         *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_ntsc_1953.png" />
+         *     <figcaption style="text-align: center;">NTSC 1953 (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        NTSC_1953,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space SMPTE C.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.630</td><td>0.310</td><td>0.155</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.340</td><td>0.595</td><td>0.070</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">SMPTE-C RGB</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{BT709} = \begin{cases} 4.5 \times C_{linear} & C_{linear} \lt 0.018 \\
+         *             1.099 \times C_{linear}^{\frac{1}{2.2}} - 0.099 & C_{linear} \ge 0.018 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{BT709}}{4.5} & C_{BT709} \lt 0.081 \\
+         *             \left( \frac{C_{BT709} + 0.099}{1.099} \right) ^{2.2} & C_{BT709} \ge 0.081 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_smpte_c.png" />
+         *     <figcaption style="text-align: center;">SMPTE-C (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        SMPTE_C,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space Adobe RGB (1998).</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.64</td><td>0.21</td><td>0.15</td><td>0.3127</td></tr>
+         *     <tr><td>y</td><td>0.33</td><td>0.71</td><td>0.06</td><td>0.3290</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Adobe RGB (1998)</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D65</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(C_{RGB} = C_{linear}^{\frac{1}{2.2}}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(C_{linear} = C_{RGB}^{2.2}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_adobe_rgb.png" />
+         *     <figcaption style="text-align: center;">Adobe RGB (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        ADOBE_RGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space ProPhoto RGB standardized as ROMM RGB ISO 22028-2:2013.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.7347</td><td>0.1596</td><td>0.0366</td><td>0.3457</td></tr>
+         *     <tr><td>y</td><td>0.2653</td><td>0.8404</td><td>0.0001</td><td>0.3585</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">ROMM RGB ISO 22028-2:2013</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{ROMM} = \begin{cases} 16 \times C_{linear} & C_{linear} \lt 0.001953 \\
+         *             C_{linear}^{\frac{1}{1.8}} & C_{linear} \ge 0.001953 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(\begin{equation}
+         *             C_{linear} = \begin{cases}\frac{C_{ROMM}}{16} & C_{ROMM} \lt 0.031248 \\
+         *             C_{ROMM}^{1.8} & C_{ROMM} \ge 0.031248 \end{cases}
+         *             \end{equation}\)
+         *         </td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([0..1]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_pro_photo_rgb.png" />
+         *     <figcaption style="text-align: center;">ProPhoto RGB (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        PRO_PHOTO_RGB,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space ACES standardized as SMPTE ST 2065-1:2012.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.73470</td><td>0.00000</td><td>0.00010</td><td>0.32168</td></tr>
+         *     <tr><td>y</td><td>0.26530</td><td>1.00000</td><td>-0.07700</td><td>0.33767</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">SMPTE ST 2065-1:2012 ACES</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(C_{ACES} = C_{linear}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(C_{linear} = C_{ACES}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_aces.png" />
+         *     <figcaption style="text-align: center;">ACES (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        ACES,
+        /**
+         * <p>{@link ColorSpace.Rgb RGB} color space ACEScg standardized as Academy S-2014-004.</p>
+         * <table summary="Color space definition">
+         *     <tr>
+         *         <th>Chromaticity</th><th>Red</th><th>Green</th><th>Blue</th><th>White point</th>
+         *     </tr>
+         *     <tr><td>x</td><td>0.713</td><td>0.165</td><td>0.128</td><td>0.32168</td></tr>
+         *     <tr><td>y</td><td>0.293</td><td>0.830</td><td>0.044</td><td>0.33767</td></tr>
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Academy S-2014-004 ACEScg</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D60</td></tr>
+         *     <tr>
+         *         <td>Opto-electronic transfer function</td>
+         *         <td colspan="4">\(C_{ACEScg} = C_{linear}\)</td>
+         *     </tr>
+         *     <tr>
+         *         <td>Electro-optical transfer function</td>
+         *         <td colspan="4">\(C_{linear} = C_{ACEScg}\)</td>
+         *     </tr>
+         *     <tr><td>Range</td><td colspan="4">\([-65504.0, 65504.0]\)</td></tr>
+         * </table>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_acescg.png" />
+         *     <figcaption style="text-align: center;">ACEScg (orange) vs sRGB (white)</figcaption>
+         * </p>
+         */
+        ACESCG,
+        /**
+         * <p>{@link Model#XYZ XYZ} color space CIE XYZ. This color space assumes standard
+         * illuminant D50 as its white point.</p>
+         * <table summary="Color space definition">
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Generic XYZ</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
+         *     <tr><td>Range</td><td colspan="4">\([-2.0, 2.0]\)</td></tr>
+         * </table>
+         */
+        CIE_XYZ,
+        /**
+         * <p>{@link Model#LAB Lab} color space CIE L*a*b*. This color space uses CIE XYZ D50
+         * as a profile conversion space.</p>
+         * <table summary="Color space definition">
+         *     <tr><th>Property</th><th colspan="4">Value</th></tr>
+         *     <tr><td>Name</td><td colspan="4">Generic L*a*b*</td></tr>
+         *     <tr><td>CIE standard illuminant</td><td colspan="4">D50</td></tr>
+         *     <tr><td>Range</td><td colspan="4">\(L: [0.0, 100.0], a: [-128, 128], b: [-128, 128]\)</td></tr>
+         * </table>
+         */
+        CIE_LAB
+        // Update the initialization block next to #get(Named) when adding new values
+    }
+
+    /**
+     * <p>A render intent determines how a {@link ColorSpace.Connector connector}
+     * maps colors from one color space to another. The choice of mapping is
+     * important when the source color space has a larger color gamut than the
+     * destination color space.</p>
+     *
+     * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent)
+     */
+    public enum RenderIntent {
+        /**
+         * <p>Compresses the source gamut into the destination gamut.
+         * This render intent affects all colors, inside and outside
+         * of destination gamut. The goal of this render intent is
+         * to preserve the visual relationship between colors.</p>
+         *
+         * <p class="note">This render intent is currently not
+         * implemented and behaves like {@link #RELATIVE}.</p>
+         */
+        PERCEPTUAL,
+        /**
+         * Similar to the {@link #ABSOLUTE} render intent, this render
+         * intent matches the closest color in the destination gamut
+         * but makes adjustments for the destination white point.
+         */
+        RELATIVE,
+        /**
+         * <p>Attempts to maintain the relative saturation of colors
+         * from the source gamut to the destination gamut, to keep
+         * highly saturated colors as saturated as possible.</p>
+         *
+         * <p class="note">This render intent is currently not
+         * implemented and behaves like {@link #RELATIVE}.</p>
+         */
+        SATURATION,
+        /**
+         * Colors that are in the destination gamut are left unchanged.
+         * Colors that fall outside of the destination gamut are mapped
+         * to the closest possible color within the gamut of the destination
+         * color space (they are clipped).
+         */
+        ABSOLUTE
+    }
+
+    /**
+     * {@usesMathJax}
+     *
+     * <p>List of adaptation matrices that can be used for chromatic adaptation
+     * using the von Kries transform. These matrices are used to convert values
+     * in the CIE XYZ space to values in the LMS space (Long Medium Short).</p>
+     *
+     * <p>Given an adaptation matrix \(A\), the conversion from XYZ to
+     * LMS is straightforward:</p>
+     *
+     * $$\left[ \begin{array}{c} L\\ M\\ S \end{array} \right] =
+     * A \left[ \begin{array}{c} X\\ Y\\ Z \end{array} \right]$$
+     *
+     * <p>The complete von Kries transform \(T\) uses a diagonal matrix
+     * noted \(D\) to perform the adaptation in LMS space. In addition
+     * to \(A\) and \(D\), the source white point \(W1\) and the destination
+     * white point \(W2\) must be specified:</p>
+     *
+     * $$\begin{align*}
+     * \left[ \begin{array}{c} L_1\\ M_1\\ S_1 \end{array} \right] &=
+     *      A \left[ \begin{array}{c} W1_X\\ W1_Y\\ W1_Z \end{array} \right] \\
+     * \left[ \begin{array}{c} L_2\\ M_2\\ S_2 \end{array} \right] &=
+     *      A \left[ \begin{array}{c} W2_X\\ W2_Y\\ W2_Z \end{array} \right] \\
+     * D &= \left[ \begin{matrix} \frac{L_2}{L_1} & 0 & 0 \\
+     *      0 & \frac{M_2}{M_1} & 0 \\
+     *      0 & 0 & \frac{S_2}{S_1} \end{matrix} \right] \\
+     * T &= A^{-1}.D.A
+     * \end{align*}$$
+     *
+     * <p>As an example, the resulting matrix \(T\) can then be used to
+     * perform the chromatic adaptation of sRGB XYZ transform from D65
+     * to D50:</p>
+     *
+     * $$sRGB_{D50} = T.sRGB_{D65}$$
+     *
+     * @see ColorSpace.Connector
+     * @see ColorSpace#connect(ColorSpace, ColorSpace)
+     */
+    public enum Adaptation {
+        /**
+         * Bradford matrix for the von Kries chromatic adaptation transform.
+         */
+        BRADFORD(new float[] {
+                 0.8951f, -0.7502f,  0.0389f,
+                 0.2664f,  1.7135f, -0.0685f,
+                -0.1614f,  0.0367f,  1.0296f
+        }),
+        /**
+         * von Kries matrix for the von Kries chromatic adaptation transform.
+         */
+        VON_KRIES(new float[] {
+                 0.40024f, -0.22630f, 0.00000f,
+                 0.70760f,  1.16532f, 0.00000f,
+                -0.08081f,  0.04570f, 0.91822f
+        });
+
+        final float[] mTransform;
+
+        Adaptation(@NonNull @Size(9) float[] transform) {
+            mTransform = transform;
+        }
+    }
+
+    /**
+     * A color model is required by a {@link ColorSpace} to describe the
+     * way colors can be represented as tuples of numbers. A common color
+     * model is the {@link #RGB RGB} color model which defines a color
+     * as represented by a tuple of 3 numbers (red, green and blue).
+     */
+    public enum Model {
+        /**
+         * The RGB model is a color model with 3 components that
+         * refer to the three additive primiaries: red, green
+         * andd blue.
+         */
+        RGB(3),
+        /**
+         * The XYZ model is a color model with 3 components that
+         * are used to model human color vision on a basic sensory
+         * level.
+         */
+        XYZ(3),
+        /**
+         * The Lab model is a color model with 3 components used
+         * to describe a color space that is more perceptually
+         * uniform than XYZ.
+         */
+        LAB(3),
+        /**
+         * The CMYK model is a color model with 4 components that
+         * refer to four inks used in color printing: cyan, magenta,
+         * yellow and black (or key). CMYK is a subtractive color
+         * model.
+         */
+        CMYK(4);
+
+        private final int mComponentCount;
+
+        Model(@IntRange(from = 1, to = 4) int componentCount) {
+            mComponentCount = componentCount;
+        }
+
+        /**
+         * Returns the number of components for this color model.
+         *
+         * @return An integer between 1 and 4
+         */
+        @IntRange(from = 1, to = 4)
+        public int getComponentCount() {
+            return mComponentCount;
+        }
+    }
+
+    private ColorSpace(
+            @NonNull String name,
+            @NonNull Model model,
+            @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+
+        if (name == null || name.length() < 1) {
+            throw new IllegalArgumentException("The name of a color space cannot be null and " +
+                    "must contain at least 1 character");
+        }
+
+        if (model == null) {
+            throw new IllegalArgumentException("A color space must have a model");
+        }
+
+        if (id < MIN_ID || id > MAX_ID) {
+            throw new IllegalArgumentException("The id must be between " +
+                    MIN_ID + " and " + MAX_ID);
+        }
+
+        mName = name;
+        mModel = model;
+        mId = id;
+    }
+
+    /**
+     * Returns the name of this color space. The name is never null
+     * and contains always at least 1 character.
+     *
+     * @return A non-null String of length >= 1
+     */
+    @NonNull
+    public String getName() {
+        return mName;
+    }
+
+    /**
+     * Returns the ID of this color space. Positive IDs match the color
+     * spaces enumerated in {@link Named}. A negative ID indicates a
+     * color space created by calling one of the public constructors.
+     *
+     * @return An integer between {@link #MIN_ID} and {@link #MAX_ID}
+     */
+    @IntRange(from = MIN_ID, to = MAX_ID)
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Return the color model of this color space.
+     *
+     * @return A non-null {@link Model}
+     *
+     * @see Model
+     * @see #getComponentCount()
+     */
+    @NonNull
+    public Model getModel() {
+        return mModel;
+    }
+
+    /**
+     * Returns the number of components that form a color value according
+     * to this color space's color model.
+     *
+     * @return An integer between 1 and 4
+     *
+     * @see Model
+     * @see #getModel()
+     */
+    @IntRange(from = 1, to = 4)
+    public int getComponentCount() {
+        return mModel.getComponentCount();
+    }
+
+    /**
+     * Returns whether this color space is a wide-gamut color space.
+     * An RGB color space is wide-gamut if its gamut entirely contains
+     * the {@link Named#SRGB sRGB} gamut and if the area of its gamut is
+     * 90% of greater than the area of the {@link Named#NTSC_1953 NTSC}
+     * gamut.
+     *
+     * @return True if this color space is a wide-gamut color space,
+     *         false otherwise
+     */
+    public abstract boolean isWideGamut();
+
+    /**
+     * <p>Indicates whether this color space is the sRGB color space or
+     * equivalent to the sRGB color space.</p>
+     * <p>A color space is considered sRGB if it meets all the following
+     * conditions:</p>
+     * <ul>
+     *     <li>Its color model is {@link Model#RGB}.</li>
+     *     <li>
+     *         Its primaries are within 1e-3 of the true
+     *         {@link Named#SRGB sRGB} primaries.
+     *     </li>
+     *     <li>
+     *         Its white point is withing 1e-3 of the CIE standard
+     *         illuminant {@link #ILLUMINANT_D65 D65}.
+     *     </li>
+     *     <li>Its opto-electronic transfer function is not linear.</li>
+     *     <li>Its electro-optical transfer function is not linear.</li>
+     *     <li>Its range is \([0..1]\).</li>
+     * </ul>
+     * <p>This method always returns true for {@link Named#SRGB}.</p>
+     *
+     * @return True if this color space is the sRGB color space (or a
+     *         close approximation), false otherwise
+     */
+    public boolean isSrgb() {
+        return false;
+    }
+
+    /**
+     * Returns the minimum valid value for the specified component of this
+     * color space's color model.
+     *
+     * @param component The index of the component
+     * @return A floating point value less than {@link #getMaxValue(int)}
+     *
+     * @see #getMaxValue(int)
+     * @see Model#getComponentCount()
+     */
+    public abstract float getMinValue(@IntRange(from = 0, to = 3) int component);
+
+    /**
+     * Returns the maximum valid value for the specified component of this
+     * color space's color model.
+     *
+     * @param component The index of the component
+     * @return A floating point value greater than {@link #getMinValue(int)}
+     *
+     * @see #getMinValue(int)
+     * @see Model#getComponentCount()
+     */
+    public abstract float getMaxValue(@IntRange(from = 0, to = 3) int component);
+
+    /**
+     * <p>Converts a color value from this color space's model to
+     * tristimulus CIE XYZ values. If the color model of this color
+     * space is not {@link Model#RGB RGB}, it is assumed that the
+     * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50}
+     * standard illuminant.</p>
+     *
+     * <p>This method is a convenience for color spaces with a model
+     * of 3 components ({@link Model#RGB RGB} or {@link Model#LAB}
+     * for instance). With color spaces using fewer or more components,
+     * use {@link #toXyz(float[])} instead</p>.
+     *
+     * @param r The first component of the value to convert from (typically R in RGB)
+     * @param g The second component of the value to convert from (typically G in RGB)
+     * @param b The third component of the value to convert from (typically B in RGB)
+     * @return A new array of 3 floats, containing tristimulus XYZ values
+     *
+     * @see #toXyz(float[])
+     * @see #fromXyz(float, float, float)
+     */
+    @NonNull
+    @Size(3)
+    public float[] toXyz(float r, float g, float b) {
+        return toXyz(new float[] { r, g, b });
+    }
+
+    /**
+     * <p>Converts a color value from this color space's model to
+     * tristimulus CIE XYZ values. If the color model of this color
+     * space is not {@link Model#RGB RGB}, it is assumed that the
+     * target CIE XYZ space uses a {@link #ILLUMINANT_D50 D50}
+     * standard illuminant.</p>
+     *
+     * <p class="note">The specified array's length  must be at least
+     * equal to to the number of color components as returned by
+     * {@link Model#getComponentCount()}.</p>
+     *
+     * @param v An array of color components containing the color space's
+     *          color value to convert to XYZ, and large enough to hold
+     *          the resulting tristimulus XYZ values
+     * @return The array passed in parameter
+     *
+     * @see #toXyz(float, float, float)
+     * @see #fromXyz(float[])
+     */
+    @NonNull
+    @Size(min = 3)
+    public abstract float[] toXyz(@NonNull @Size(min = 3) float[] v);
+
+    /**
+     * <p>Converts tristimulus values from the CIE XYZ space to this
+     * color space's color model.</p>
+     *
+     * @param x The X component of the color value
+     * @param y The Y component of the color value
+     * @param z The Z component of the color value
+     * @return A new array whose size is equal to the number of color
+     *         components as returned by {@link Model#getComponentCount()}
+     *
+     * @see #fromXyz(float[])
+     * @see #toXyz(float, float, float)
+     */
+    @NonNull
+    @Size(min = 3)
+    public float[] fromXyz(float x, float y, float z) {
+        float[] xyz = new float[mModel.getComponentCount()];
+        xyz[0] = x;
+        xyz[1] = y;
+        xyz[2] = z;
+        return fromXyz(xyz);
+    }
+
+    /**
+     * <p>Converts tristimulus values from the CIE XYZ space to this color
+     * space's color model. The resulting value is passed back in the specified
+     * array.</p>
+     *
+     * <p class="note>The specified array's length  must be at least equal to
+     * to the number of color components as returned by
+     * {@link Model#getComponentCount()}, and its first 3 values must
+     * be the XYZ components to convert from.</p>
+     *
+     * @param v An array of color components containing the XYZ values
+     *          to convert from, and large enough to hold the number
+     *          of components of this color space's model
+     * @return The array passed in parameter
+     *
+     * @see #fromXyz(float, float, float)
+     * @see #toXyz(float[])
+     */
+    @NonNull
+    @Size(min = 3)
+    public abstract float[] fromXyz(@NonNull @Size(min = 3) float[] v);
+
+    /**
+     * <p>Returns a string representation of the object. This method returns
+     * a string equal to the value of:</p>
+     *
+     * <pre class="prettyprint">
+     * getName() + "(id=" + getId() + ", model=" + getModel() + ")"
+     * </pre>
+     *
+     * <p>For instance, the string representation of the {@link Named#SRGB sRGB}
+     * color space is equal to the following value:</p>
+     *
+     * <pre>
+     * sRGB IEC61966-2.1 (id=0, model=RGB)
+     * </pre>
+     *
+     * @return A string representation of the object
+     */
+    @Override
+    public String toString() {
+        return mName + " (id=" + mId + ", model=" + mModel + ")";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        ColorSpace that = (ColorSpace) o;
+
+        if (mId != that.mId) return false;
+        //noinspection SimplifiableIfStatement
+        if (!mName.equals(that.mName)) return false;
+        return mModel == that.mModel;
+
+    }
+
+    @Override
+    public int hashCode() {
+        int result = mName.hashCode();
+        result = 31 * result + mModel.hashCode();
+        result = 31 * result + mId;
+        return result;
+    }
+
+    /**
+     * <p>Connects two color spaces to allow conversion from the source color
+     * space to the destination color space. If the source and destination
+     * color spaces do not have the same profile connection space (CIE XYZ
+     * with the same white point), they are chromatically adapted to use the
+     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
+     *
+     * <p>If the source and destination are the same, an optimized connector
+     * is returned to avoid unnecessary computations and loss of precision.</p>
+     *
+     * <p>Colors are mapped from the source color space to the destination color
+     * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p>
+     *
+     * @param source The color space to convert colors from
+     * @param destination The color space to convert colors to
+     * @return A non-null connector between the two specified color spaces
+     *
+     * @see #connect(ColorSpace)
+     * @see #connect(ColorSpace, RenderIntent)
+     * @see #connect(ColorSpace, ColorSpace, RenderIntent)
+     */
+    @NonNull
+    public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination) {
+        return connect(source, destination, RenderIntent.PERCEPTUAL);
+    }
+
+    /**
+     * <p>Connects two color spaces to allow conversion from the source color
+     * space to the destination color space. If the source and destination
+     * color spaces do not have the same profile connection space (CIE XYZ
+     * with the same white point), they are chromatically adapted to use the
+     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
+     *
+     * <p>If the source and destination are the same, an optimized connector
+     * is returned to avoid unnecessary computations and loss of precision.</p>
+     *
+     * @param source The color space to convert colors from
+     * @param destination The color space to convert colors to
+     * @param intent The render intent to map colors from the source to the destination
+     * @return A non-null connector between the two specified color spaces
+     *
+     * @see #connect(ColorSpace)
+     * @see #connect(ColorSpace, RenderIntent)
+     * @see #connect(ColorSpace, ColorSpace)
+     */
+    @NonNull
+    @SuppressWarnings("ConstantConditions")
+    public static Connector connect(@NonNull ColorSpace source, @NonNull ColorSpace destination,
+            @NonNull RenderIntent intent) {
+        if (source.equals(destination)) return Connector.identity(source);
+
+        if (source.getModel() == Model.RGB && destination.getModel() == Model.RGB) {
+            return new Connector.Rgb((Rgb) source, (Rgb) destination, intent);
+        }
+
+        return new Connector(source, destination, intent);
+    }
+
+    /**
+     * <p>Connects the specified color spaces to sRGB.
+     * If the source color space does not use CIE XYZ D65 as its profile
+     * connection space, the two spaces are chromatically adapted to use the
+     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
+     *
+     * <p>If the source is the sRGB color space, an optimized connector
+     * is returned to avoid unnecessary computations and loss of precision.</p>
+     *
+     * <p>Colors are mapped from the source color space to the destination color
+     * space using the {@link RenderIntent#PERCEPTUAL perceptual} render intent.</p>
+     *
+     * @param source The color space to convert colors from
+     * @return A non-null connector between the specified color space and sRGB
+     *
+     * @see #connect(ColorSpace, RenderIntent)
+     * @see #connect(ColorSpace, ColorSpace)
+     * @see #connect(ColorSpace, ColorSpace, RenderIntent)
+     */
+    @NonNull
+    public static Connector connect(@NonNull ColorSpace source) {
+        return connect(source, RenderIntent.PERCEPTUAL);
+    }
+
+    /**
+     * <p>Connects the specified color spaces to sRGB.
+     * If the source color space does not use CIE XYZ D65 as its profile
+     * connection space, the two spaces are chromatically adapted to use the
+     * CIE standard illuminant {@link #ILLUMINANT_D50 D50} as needed.</p>
+     *
+     * <p>If the source is the sRGB color space, an optimized connector
+     * is returned to avoid unnecessary computations and loss of precision.</p>
+     *
+     * @param source The color space to convert colors from
+     * @param intent The render intent to map colors from the source to the destination
+     * @return A non-null connector between the specified color space and sRGB
+     *
+     * @see #connect(ColorSpace)
+     * @see #connect(ColorSpace, ColorSpace)
+     * @see #connect(ColorSpace, ColorSpace, RenderIntent)
+     */
+    @NonNull
+    public static Connector connect(@NonNull ColorSpace source, @NonNull RenderIntent intent) {
+        if (source.isSrgb()) return Connector.identity(source);
+
+        if (source.getModel() == Model.RGB) {
+            return new Connector.Rgb((Rgb) source, (Rgb) get(Named.SRGB), intent);
+        }
+
+        return new Connector(source, get(Named.SRGB), intent);
+    }
+
+    /**
+     * <p>Performs the chromatic adaptation of a color space from its native
+     * white point to the specified white point.</p>
+     *
+     * <p>The chromatic adaptation is performed using the
+     * {@link Adaptation#BRADFORD} matrix.</p>
+     *
+     * <p class="note">The color space returned by this method always has
+     * an ID of {@link #MIN_ID}.</p>
+     *
+     * @param colorSpace The color space to chromatically adapt
+     * @param whitePoint The new white point
+     * @return A {@link ColorSpace} instance with the same name, primaries,
+     *         transfer functions and range as the specified color space
+     *
+     * @see Adaptation
+     * @see #adapt(ColorSpace, float[], Adaptation)
+     */
+    @NonNull
+    public static ColorSpace adapt(@NonNull ColorSpace colorSpace,
+            @NonNull @Size(min = 2, max = 3) float[] whitePoint) {
+        return adapt(colorSpace, whitePoint, Adaptation.BRADFORD);
+    }
+
+    /**
+     * <p>Performs the chromatic adaptation of a color space from its native
+     * white point to the specified white point. If the specified color space
+     * does not have an {@link Model#RGB RGB} color model, or if the color
+     * space already has the target white point, the color space is returned
+     * unmodified.</p>
+     *
+     * <p>The chromatic adaptation is performed using the von Kries method
+     * described in the documentation of {@link Adaptation}.</p>
+     *
+     * <p class="note">The color space returned by this method always has
+     * an ID of {@link #MIN_ID}.</p>
+     *
+     * @param colorSpace The color space to chromatically adapt
+     * @param whitePoint The new white point
+     * @param adaptation The adaptation matrix
+     * @return A new color space if the specified color space has an RGB
+     *         model and a white point different from the specified white
+     *         point; the specified color space otherwise
+     *
+     * @see Adaptation
+     * @see #adapt(ColorSpace, float[])
+     */
+    @NonNull
+    public static ColorSpace adapt(@NonNull ColorSpace colorSpace,
+            @NonNull @Size(min = 2, max = 3) float[] whitePoint,
+            @NonNull Adaptation adaptation) {
+        if (colorSpace.getModel() == Model.RGB) {
+            ColorSpace.Rgb rgb = (ColorSpace.Rgb) colorSpace;
+            if (compare(rgb.mWhitePoint, whitePoint)) return colorSpace;
+
+            float[] xyz = whitePoint.length == 3 ?
+                    Arrays.copyOf(whitePoint, 3) : xyYToXyz(whitePoint);
+            float[] adaptationTransform = chromaticAdaptation(adaptation.mTransform,
+                    xyYToXyz(rgb.getWhitePoint()), xyz);
+            float[] transform = mul3x3(adaptationTransform, rgb.mTransform);
+
+            return new ColorSpace.Rgb(rgb, transform, whitePoint);
+        }
+        return colorSpace;
+    }
+
+    /**
+     * <p>Returns an instance of {@link ColorSpace} whose ID matches the specified
+     * ID. If the ID is < 0 or &gt; {@link #MAX_ID}, calling this method is equivalent
+     * to calling <code>get(Named.SRGB)</code>.</p>
+     *
+     * <p>This method always returns the same instance for a given ID.</p>
+     *
+     * <p>This method is thread-safe.</p>
+     *
+     * @param index An integer ID between {@link #MIN_ID} and {@link #MAX_ID}
+     * @return A non-null {@link ColorSpace} instance
+     */
+    @NonNull
+    static ColorSpace get(@IntRange(from = MIN_ID, to = MAX_ID) int index) {
+        if (index < 0 || index > Named.values().length) {
+            return get(Named.SRGB);
+        }
+        return sNamedColorSpaces[index];
+    }
+
+    /**
+     * <p>Returns an instance of {@link ColorSpace} identified by the specified
+     * name. The list of names provided in the {@link Named} enum gives access
+     * to a variety of common RGB color spaces.</p>
+     *
+     * <p>This method always returns the same instance for a given name.</p>
+     *
+     * <p>This method is thread-safe.</p>
+     *
+     * @param name The name of the color space to get an instance of
+     * @return A non-null {@link ColorSpace} instance
+     */
+    @NonNull
+    public static ColorSpace get(@NonNull Named name) {
+        return sNamedColorSpaces[name.ordinal()];
+    }
+
+    static {
+        sNamedColorSpaces[Named.SRGB.ordinal()] = new ColorSpace.Rgb(
+                "sRGB IEC61966-2.1",
+                SRGB_PRIMARIES,
+                ILLUMINANT_D65,
+                x -> rcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
+                x -> response(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
+                0.0f, 1.0f,
+                Named.SRGB.ordinal()
+        );
+        sNamedColorSpaces[Named.LINEAR_SRGB.ordinal()] = new ColorSpace.Rgb(
+                "sRGB IEC61966-2.1 (Linear)",
+                SRGB_PRIMARIES,
+                ILLUMINANT_D65,
+                DoubleUnaryOperator.identity(),
+                DoubleUnaryOperator.identity(),
+                0.0f, 1.0f,
+                Named.LINEAR_SRGB.ordinal()
+        );
+        sNamedColorSpaces[Named.EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
+                "scRGB-nl IEC 61966-2-2:2003",
+                SRGB_PRIMARIES,
+                ILLUMINANT_D65,
+                x -> absRcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
+                x -> absResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
+                -0.5f, 7.5f,
+                Named.EXTENDED_SRGB.ordinal()
+        );
+        sNamedColorSpaces[Named.LINEAR_EXTENDED_SRGB.ordinal()] = new ColorSpace.Rgb(
+                "scRGB- IEC 61966-2-2:2003",
+                SRGB_PRIMARIES,
+                ILLUMINANT_D65,
+                DoubleUnaryOperator.identity(),
+                DoubleUnaryOperator.identity(),
+                -0.5f, 7.5f,
+                Named.LINEAR_EXTENDED_SRGB.ordinal()
+        );
+        sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
+                "Rec. ITU-R BT.709-5",
+                new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
+                ILLUMINANT_D65,
+                x -> rcpResponse(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
+                x -> response(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
+                0.0f, 1.0f,
+                Named.BT709.ordinal()
+        );
+        sNamedColorSpaces[Named.BT2020.ordinal()] = new ColorSpace.Rgb(
+                "Rec. ITU-R BT.2020-1",
+                new float[] { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f },
+                ILLUMINANT_D65,
+                x -> rcpResponse(x, 1 / 0.45, 1 / 1.0993, 0.0993 / 1.0993, 1 / 4.5, 0.08145),
+                x -> response(x, 1 / 0.45, 1 / 1.0993, 0.099 / 1.0993, 1 / 4.5, 0.08145),
+                0.0f, 1.0f,
+                Named.BT2020.ordinal()
+        );
+        sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
+                "SMPTE RP 431-2-2007 DCI (P3)",
+                new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
+                new float[] { 0.314f, 0.351f },
+                x -> Math.pow(x, 1 / 2.6),
+                x -> Math.pow(x, 2.6),
+                0.0f, 1.0f,
+                Named.DCI_P3.ordinal()
+        );
+        sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
+                "Display P3",
+                new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
+                ILLUMINANT_D65,
+                x -> rcpResponse(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
+                x -> response(x, 2.4, 1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045),
+                0.0f, 1.0f,
+                Named.DISPLAY_P3.ordinal()
+        );
+        sNamedColorSpaces[Named.NTSC_1953.ordinal()] = new ColorSpace.Rgb(
+                "NTSC (1953)",
+                NTSC_1953_PRIMARIES,
+                ILLUMINANT_C,
+                x -> rcpResponse(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
+                x -> response(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
+                0.0f, 1.0f,
+                Named.NTSC_1953.ordinal()
+        );
+        sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb(
+                "SMPTE-C RGB",
+                new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f },
+                ILLUMINANT_D65,
+                x -> rcpResponse(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
+                x -> response(x, 1 / 0.45, 1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081),
+                0.0f, 1.0f,
+                Named.SMPTE_C.ordinal()
+        );
+        sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb(
+                "Adobe RGB (1998)",
+                new float[] { 0.64f, 0.33f, 0.21f, 0.71f, 0.15f, 0.06f },
+                ILLUMINANT_D65,
+                x -> Math.pow(x, 1 / 2.2),
+                x -> Math.pow(x, 2.2),
+                0.0f, 1.0f,
+                Named.ADOBE_RGB.ordinal()
+        );
+        sNamedColorSpaces[Named.PRO_PHOTO_RGB.ordinal()] = new ColorSpace.Rgb(
+                "ROMM RGB ISO 22028-2:2013",
+                new float[] { 0.7347f, 0.2653f, 0.1596f, 0.8404f, 0.0366f, 0.0001f },
+                ILLUMINANT_D50,
+                x -> rcpResponse(x, 1.8, 1.0, 0.0, 1 / 16.0, 0.031248),
+                x -> response(x, 1.8, 1.0, 0.0, 1 / 16.0, 0.031248),
+                0.0f, 1.0f,
+                Named.PRO_PHOTO_RGB.ordinal()
+        );
+        sNamedColorSpaces[Named.ACES.ordinal()] = new ColorSpace.Rgb(
+                "SMPTE ST 2065-1:2012 ACES",
+                new float[] { 0.73470f, 0.26530f, 0.0f, 1.0f, 0.00010f, -0.0770f },
+                ILLUMINANT_D60,
+                DoubleUnaryOperator.identity(),
+                DoubleUnaryOperator.identity(),
+                -65504.0f, 65504.0f,
+                Named.ACES.ordinal()
+        );
+        sNamedColorSpaces[Named.ACESCG.ordinal()] = new ColorSpace.Rgb(
+                "Academy S-2014-004 ACEScg",
+                new float[] { 0.713f, 0.293f, 0.165f, 0.830f, 0.128f, 0.044f },
+                ILLUMINANT_D60,
+                DoubleUnaryOperator.identity(),
+                DoubleUnaryOperator.identity(),
+                -65504.0f, 65504.0f,
+                Named.ACESCG.ordinal()
+        );
+        sNamedColorSpaces[Named.CIE_XYZ.ordinal()] = new Xyz(
+                "Generic XYZ",
+                Named.CIE_XYZ.ordinal()
+        );
+        sNamedColorSpaces[Named.CIE_LAB.ordinal()] = new ColorSpace.Lab(
+                "Generic L*a*b*",
+                Named.CIE_LAB.ordinal()
+        );
+    }
+
+    // Reciprocal piecewise gamma response
+    private static double rcpResponse(double x, double g,double a, double b, double c, double d) {
+        return x >= d * c ? (Math.pow(x, 1.0 / g) - b) / a : x / c;
+    }
+
+    // Piecewise gamma response
+    private static double response(double x, double g, double a, double b, double c, double d) {
+        return x >= d ? Math.pow(a * x + b, g) : c * x;
+    }
+
+    // Reciprocal piecewise gamma response, encoded as sign(x).f(abs(x)) for color
+    // spaces that allow negative values
+    @SuppressWarnings("SameParameterValue")
+    private static double absRcpResponse(double x, double g, double a, double b, double c, double d) {
+        return Math.copySign(rcpResponse(x < 0.0 ? -x : x, g, a, b, c, d), x);
+    }
+
+    // Piecewise gamma response, encoded as sign(x).f(abs(x)) for color spaces that
+    // allow negative values
+    @SuppressWarnings("SameParameterValue")
+    private static double absResponse(double x, double g, double a, double b, double c, double d) {
+        return Math.copySign(response(x < 0.0 ? -x : x, g, a, b, c, d), x);
+    }
+
+    /**
+     * Compares two arrays of float with a precision of 1e-3.
+     *
+     * @param a The first array to compare
+     * @param b The second array to compare
+     * @return True if the two arrays are equal, false otherwise
+     */
+    private static boolean compare(@NonNull float[] a, @NonNull float[] b) {
+        if (a == b) return true;
+        for (int i = 0; i < a.length; i++) {
+            if (Float.compare(a[i], b[i]) != 0 && Math.abs(a[i] - b[i]) > 1e-3f) return false;
+        }
+        return true;
+    }
+
+    /**
+     * Inverts a 3x3 matrix. This method assumes the matrix is invertible.
+     *
+     * @param m A 3x3 matrix as a non-null array of 9 floats
+     * @return A new array of 9 floats containing the inverse of the input matrix
+     */
+    @NonNull
+    @Size(9)
+    private static float[] inverse3x3(@NonNull @Size(9) float[] m) {
+        float a = m[0];
+        float b = m[3];
+        float c = m[6];
+        float d = m[1];
+        float e = m[4];
+        float f = m[7];
+        float g = m[2];
+        float h = m[5];
+        float i = m[8];
+
+        float A = e * i - f * h;
+        float B = f * g - d * i;
+        float C = d * h - e * g;
+
+        float det = a * A + b * B + c * C;
+
+        float inverted[] = new float[m.length];
+        inverted[0] = A / det;
+        inverted[1] = B / det;
+        inverted[2] = C / det;
+        inverted[3] = (c * h - b * i) / det;
+        inverted[4] = (a * i - c * g) / det;
+        inverted[5] = (b * g - a * h) / det;
+        inverted[6] = (b * f - c * e) / det;
+        inverted[7] = (c * d - a * f) / det;
+        inverted[8] = (a * e - b * d) / det;
+        return inverted;
+    }
+
+    /**
+     * Multiplies two 3x3 matrices, represented as non-null arrays of 9 floats.
+     *
+     * @param lhs 3x3 matrix, as a non-null array of 9 floats
+     * @param rhs 3x3 matrix, as a non-null array of 9 floats
+     * @return A new array of 9 floats containing the result of the multiplication
+     *         of rhs by lhs
+     */
+    @NonNull
+    @Size(9)
+    private static float[] mul3x3(@NonNull @Size(9) float[] lhs, @NonNull @Size(9) float[] rhs) {
+        float[] r = new float[9];
+        r[0] = lhs[0] * rhs[0] + lhs[3] * rhs[1] + lhs[6] * rhs[2];
+        r[1] = lhs[1] * rhs[0] + lhs[4] * rhs[1] + lhs[7] * rhs[2];
+        r[2] = lhs[2] * rhs[0] + lhs[5] * rhs[1] + lhs[8] * rhs[2];
+        r[3] = lhs[0] * rhs[3] + lhs[3] * rhs[4] + lhs[6] * rhs[5];
+        r[4] = lhs[1] * rhs[3] + lhs[4] * rhs[4] + lhs[7] * rhs[5];
+        r[5] = lhs[2] * rhs[3] + lhs[5] * rhs[4] + lhs[8] * rhs[5];
+        r[6] = lhs[0] * rhs[6] + lhs[3] * rhs[7] + lhs[6] * rhs[8];
+        r[7] = lhs[1] * rhs[6] + lhs[4] * rhs[7] + lhs[7] * rhs[8];
+        r[8] = lhs[2] * rhs[6] + lhs[5] * rhs[7] + lhs[8] * rhs[8];
+        return r;
+    }
+
+    /**
+     * Multiplies a vector of 3 components by a 3x3 matrix and stores the
+     * result in the input vector.
+     *
+     * @param lhs 3x3 matrix, as a non-null array of 9 floats
+     * @param rhs Vector of 3 components, as a non-null array of 3 floats
+     * @return The array of 3 passed as the rhs parameter
+     */
+    @NonNull
+    @Size(min = 3)
+    private static float[] mul3x3Float3(
+            @NonNull @Size(9) float[] lhs, @NonNull @Size(min = 3) float[] rhs) {
+        float r0 = rhs[0];
+        float r1 = rhs[1];
+        float r2 = rhs[2];
+        rhs[0] = lhs[0] * r0 + lhs[3] * r1 + lhs[6] * r2;
+        rhs[1] = lhs[1] * r0 + lhs[4] * r1 + lhs[7] * r2;
+        rhs[2] = lhs[2] * r0 + lhs[5] * r1 + lhs[8] * r2;
+        return rhs;
+    }
+
+    /**
+     * Multiplies a diagonal 3x3 matrix lhs, represented as an array of 3 floats,
+     * by a 3x3 matrix represented as an array of 9 floats.
+     *
+     * @param lhs Diagonal 3x3 matrix, as a non-null array of 3 floats
+     * @param rhs 3x3 matrix, as a non-null array of 9 floats
+     * @return A new array of 9 floats containing the result of the multiplication
+     *         of rhs by lhs
+     */
+    @NonNull
+    @Size(9)
+    private static float[] mul3x3Diag(
+            @NonNull @Size(3) float[] lhs, @NonNull @Size(9) float[] rhs) {
+        return new float[] {
+                lhs[0] * rhs[0], lhs[1] * rhs[1], lhs[2] * rhs[2],
+                lhs[0] * rhs[3], lhs[1] * rhs[4], lhs[2] * rhs[5],
+                lhs[0] * rhs[6], lhs[1] * rhs[7], lhs[2] * rhs[8]
+        };
+    }
+
+    /**
+     * Converts a value from CIE xyY to CIE XYZ. Y is assumed to be 1 so the
+     * input xyY array only contains the x and y components.
+     *
+     * @param xyY The xyY value to convert to XYZ, cannot be null, length must be 2
+     * @return A new float array of length 3 containing XYZ values
+     */
+    @NonNull
+    @Size(3)
+    private static float[] xyYToXyz(@NonNull @Size(2) float[] xyY) {
+        return new float[] { xyY[0] / xyY[1], 1.0f, (1 - xyY[0] - xyY[1]) / xyY[1] };
+    }
+
+    /**
+     * <p>Computes the chromatic adaptation transform from the specified
+     * source white point to the specified destination white point.</p>
+     *
+     * <p>The transform is computed using the von Kris method, described
+     * in more details in the documentation of {@link Adaptation}. The
+     * {@link Adaptation} enum provides different matrices that can be
+     * used to perform the adaptation.</p>
+     *
+     * @param matrix The adaptation matrix
+     * @param srcWhitePoint The white point to adapt from, *will be modified*
+     * @param dstWhitePoint The white point to adapt to, *will be modified*
+     * @return A 3x3 matrix as a non-null array of 9 floats
+     */
+    @NonNull
+    @Size(9)
+    private static float[] chromaticAdaptation(@NonNull @Size(9) float[] matrix,
+            @NonNull @Size(3) float[] srcWhitePoint, @NonNull @Size(3) float[] dstWhitePoint) {
+        float[] srcLMS = mul3x3Float3(matrix, srcWhitePoint);
+        float[] dstLMS = mul3x3Float3(matrix, dstWhitePoint);
+        // LMS is a diagonal matrix stored as a float[3]
+        float[] LMS = { dstLMS[0] / srcLMS[0], dstLMS[1] / srcLMS[1], dstLMS[2] / srcLMS[2] };
+        return mul3x3(inverse3x3(matrix), mul3x3Diag(LMS, matrix));
+    }
+
+    /**
+     * Implementation of the CIE XYZ color space. Assumes the white point is D50.
+     */
+    private static final class Xyz extends ColorSpace {
+        private Xyz(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+            super(name, Model.XYZ, id);
+        }
+
+        @Override
+        public boolean isWideGamut() {
+            return true;
+        }
+
+        @Override
+        public float getMinValue(@IntRange(from = 0, to = 3) int component) {
+            return -2.0f;
+        }
+
+        @Override
+        public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
+            return 2.0f;
+        }
+
+        @Override
+        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
+            v[0] = clamp(v[0]);
+            v[1] = clamp(v[1]);
+            v[2] = clamp(v[2]);
+            return v;
+        }
+
+        @Override
+        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
+            v[0] = clamp(v[0]);
+            v[1] = clamp(v[1]);
+            v[2] = clamp(v[2]);
+            return v;
+        }
+
+        private static float clamp(float x) {
+            return x < -2.0f ? -2.0f : x > 2.0f ? 2.0f : x;
+        }
+    }
+
+    /**
+     * Implementation of the CIE L*a*b* color space. Its PCS is CIE XYZ
+     * with a white point of D50.
+     */
+    private static final class Lab extends ColorSpace {
+        private static final float A = 216.0f / 24389.0f;
+        private static final float B = 841.0f / 108.0f;
+        private static final float C = 4.0f / 29.0f;
+        private static final float D = 6.0f / 29.0f;
+
+        private Lab(@NonNull String name, @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+            super(name, Model.LAB, id);
+        }
+
+        @Override
+        public boolean isWideGamut() {
+            return true;
+        }
+
+        @Override
+        public float getMinValue(@IntRange(from = 0, to = 3) int component) {
+            return component == 0 ? 0.0f : -128.0f;
+        }
+
+        @Override
+        public float getMaxValue(@IntRange(from = 0, to = 3) int component) {
+            return component == 0 ? 100.0f : 128.0f;
+        }
+
+        @Override
+        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
+            v[0] = clamp(v[0], 0.0f, 100.0f);
+            v[1] = clamp(v[1], -128.0f, 128.0f);
+            v[2] = clamp(v[2], -128.0f, 128.0f);
+
+            float fy = (v[0] + 16.0f) / 116.0f;
+            float fx = fy + (v[1] * 0.002f);
+            float fz = fy - (v[2] * 0.005f);
+            float X = fx > D ? fx * fx * fx : (1.0f / B) * (fx - C);
+            float Y = fy > D ? fy * fy * fy : (1.0f / B) * (fy - C);
+            float Z = fz > D ? fz * fz * fz : (1.0f / B) * (fz - C);
+
+            v[0] = X * ILLUMINANT_D50_XYZ[0];
+            v[1] = Y * ILLUMINANT_D50_XYZ[1];
+            v[2] = Z * ILLUMINANT_D50_XYZ[2];
+
+            return v;
+        }
+
+        @Override
+        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
+            float X = v[0] / ILLUMINANT_D50_XYZ[0];
+            float Y = v[1] / ILLUMINANT_D50_XYZ[1];
+            float Z = v[2] / ILLUMINANT_D50_XYZ[2];
+
+            float fx = X > A ? (float) Math.pow(X, 1.0 / 3.0) : B * X + C;
+            float fy = Y > A ? (float) Math.pow(Y, 1.0 / 3.0) : B * Y + C;
+            float fz = Z > A ? (float) Math.pow(Z, 1.0 / 3.0) : B * Z + C;
+
+            float L = 116.0f * fy - 16.0f;
+            float a = 500.0f * (fx - fy);
+            float b = 200.0f * (fy - fz);
+
+            v[0] = clamp(L, 0.0f, 100.0f);
+            v[1] = clamp(a, -128.0f, 128.0f);
+            v[2] = clamp(b, -128.0f, 128.0f);
+
+            return v;
+        }
+
+        private static float clamp(float x, float min, float max) {
+            return x < min ? min : x > max ? max : x;
+        }
+    }
+
+    /**
+     * {@usesMathJax}
+     *
+     * <p>An RGB color space is an additive color space using the
+     * {@link Model#RGB RGB} color model (a color is therefore represented
+     * by a tuple of 3 numbers).</p>
+     *
+     * <p>A specific RGB color space is defined by the following properties:</p>
+     * <ul>
+     *     <li>Three chromaticities of the red, green and blue primaries, which
+     *     define the gamut of the color space.</li>
+     *     <li>A white point chromaticity that defines the stimulus to which
+     *     color space values are normalized (also just called "white").</li>
+     *     <li>An opto-electronic transfer function, also called opto-electronic
+     *     conversion function or often, and approximately, gamma function.</li>
+     *     <li>An electro-optical transfer function, also called electo-optical
+     *     conversion function or often, and approximately, gamma function.</li>
+     *     <li>A range of valid RGB values (most commonly \([0..1]\)).</li>
+     * </ul>
+     *
+     * <p>The most commonly used RGB color space is {@link Named#SRGB sRGB}.</p>
+     *
+     * <h3>Primaries and white point chromaticities</h3>
+     * <p>In this implementation, the chromaticity of the primaries and the white
+     * point of an RGB color space is defined in the CIE xyY color space. This
+     * color space separates the chromaticity of a color, the x and y components,
+     * and its luminance, the Y component. Since the primaries and the white
+     * point have full brightness, the Y component is assumed to be 1 and only
+     * the x and y components are needed to encode them.</p>
+     * <p>For convenience, this implementation also allows to define the
+     * primaries and white point in the CIE XYZ space. The tristimulus XYZ values
+     * are internally converted to xyY.</p>
+     *
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/colorspace_srgb.png" />
+     *     <figcaption style="text-align: center;">sRGB primaries and white point</figcaption>
+     * </p>
+     *
+     * <h3>Transfer functions</h3>
+     * <p>A transfer function is a color component conversion function, defined as
+     * a single variable, monotonic mathematical function. It is applied to each
+     * individual component of a color. They are used to perform the mapping
+     * between linear tristimulus values and non-linear electronic signal value.</p>
+     * <p>The <em>opto-electronic transfer function</em> (OETF or OECF) encodes
+     * tristimulus values in a scene to a non-linear electronic signal value.
+     * An OETF is often expressed as a power function with an exponent between
+     * 0.38 and 0.55 (the reciprocal of 1.8 to 2.6).</p>
+     * <p>The <em>electro-optical transfer function</em> (EOTF or EOCF) decodes
+     * a non-linear electronic signal value to a tristimulus value at the display.
+     * An EOTF is often expressed as a power function with an exponent between
+     * 1.8 and 2.6.</p>
+     * <p>Transfer functions are used as a compression scheme. For instance,
+     * linear sRGB values would normally require 11 to 12 bits of precision to
+     * store all values that can be perceived by the human eye. When encoding
+     * sRGB values using the appropriate OETF (see {@link Named#SRGB sRGB} for
+     * an exact mathematical description of that OETF), the values can be
+     * compressed to only 8 bits precision.</p>
+     * <p>When manipulating RGB values, particularly sRGB values, it is safe
+     * to assume that these values have been encoded with the appropriate
+     * OETF (unless noted otherwise). Encoded values are often said to be in
+     * "gamma space". They are therefore defined in a non-linear space. This
+     * in turns means that any linear operation applied to these values is
+     * going to yield mathematically incorrect results (any linear interpolation
+     * such as gradient generation for instance, most image processing functions
+     * such as blurs, etc.).</p>
+     * <p>To properly process encoded RGB values you must first apply the
+     * EOTF to decode the value into linear space. After processing, the RGB
+     * value must be encoded back to non-linear ("gamma") space. Here is a
+     * formal description of the process, where \(f\) is the processing
+     * function to apply:</p>
+     *
+     * $$RGB_{out} = OETF(f(EOTF(RGB_{in})))$$
+     *
+     * <p class="note">Some RGB color spaces, such as {@link Named#ACES} and
+     * {@link Named#LINEAR_EXTENDED_SRGB scRGB}, are said to be linear because
+     * their transfer functions are the identity function: \(f(x) = x\).
+     * If the source and/or destination are known to be linear, it is not
+     * necessary to invoke the transfer functions.</p>
+     *
+     * <h3>Range</h3>
+     * <p>Most RGB color spaces allow RGB values in the range \([0..1]\). There
+     * are however a few RGB color spaces that allow much larger ranges. For
+     * instance, {@link Named#EXTENDED_SRGB scRGB} is used to manipulate the
+     * range \([-0.5..7.5]\) while {@link Named#ACES ACES} can be used throughout
+     * the range \([-65504, 65504]\).</p>
+     *
+     * <p>
+     *     <img src="{@docRoot}reference/android/images/graphics/colorspace_scrgb.png" />
+     *     <figcaption style="text-align: center;">Extended sRGB and its large range</figcaption>
+     * </p>
+     *
+     * <h3>Converting between RGB color spaces</h3>
+     * <p>Conversion between two color spaces is achieved by using an intermediate
+     * color space called the profile connection space (PCS). The PCS used by
+     * this implementation is CIE XYZ. The conversion operation is defined
+     * as such:</p>
+     *
+     * $$RGB_{out} = OETF(T_{dst}^{-1} \cdot T_{src} \cdot EOTF(RGB_{in}))$$
+     *
+     * <p>Where \(T_{src}\) is the {@link #getTransform() RGB to XYZ transform}
+     * of the source color space and \(T_{dst}^{-1}\) the {@link #getInverseTransform()
+     * XYZ to RGB transform} of the destination color space.</p>
+     * <p>Many RGB color spaces commonly used with electronic devices use the
+     * standard illuminant {@link #ILLUMINANT_D65 D65}. Care must be take however
+     * when converting between two RGB color spaces if their white points do not
+     * match. This can be achieved by either calling
+     * {@link #adapt(ColorSpace, float[])} to adapt one or both color spaces to
+     * a single common white point. This can be achieved automatically by calling
+     * {@link ColorSpace#connect(ColorSpace, ColorSpace)}, which also handles
+     * non-RGB color spaces.</p>
+     * <p>To learn more about the white point adaptation process, refer to the
+     * documentation of {@link Adaptation}.</p>
+     */
+    public static class Rgb extends ColorSpace {
+        @NonNull private final float[] mWhitePoint;
+        @NonNull private final float[] mPrimaries;
+        @NonNull private final float[] mTransform;
+        @NonNull private final float[] mInverseTransform;
+
+        @NonNull private final boolean mIsWideGamut;
+        @NonNull private final boolean mIsSrgb;
+
+        @NonNull private final DoubleUnaryOperator mOetf;
+        @NonNull private final DoubleUnaryOperator mEotf;
+        @NonNull private final DoubleUnaryOperator mClampedOetf;
+        @NonNull private final DoubleUnaryOperator mClampedEotf;
+
+        private final float mMin;
+        private final float mMax;
+
+        /**
+         * <p>Creates a new RGB color space using a 3x3 column-major transform matrix.
+         * The transform matrix must convert from the RGB space to the profile connection
+         * space CIE XYZ.</p>
+         *
+         * <p class="note">The range of the color space is imposed to be \([0..1]\).</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param toXYZ 3x3 column-major transform matrix from RGB to the profile
+         *              connection space CIE XYZ as an array of 9 floats, cannot be null
+         * @param oetf Opto-electronic transfer function, cannot be null
+         * @param eotf Electro-optical transfer function, cannot be null
+         *
+         * @throws IllegalArgumentException If any of the following conditions is met:
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>The OETF is null or the EOTF is null.</li>
+         *     <li>The minimum valid value is >= the maximum valid value.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        public Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(9) float[] toXYZ,
+                @NonNull DoubleUnaryOperator oetf,
+                @NonNull DoubleUnaryOperator eotf) {
+            this(name, computePrimaries(toXYZ, eotf), computeWhitePoint(toXYZ, eotf),
+                    oetf, eotf, 0.0f, 1.0f, MIN_ID);
+        }
+
+        /**
+         * <p>Creates a new RGB color space using a specified set of primaries
+         * and a specified white point.</p>
+         *
+         * <p>The primaries and white point can be specified in the CIE xyY space
+         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
+         *
+         * <table summary="Parameters length">
+         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
+         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
+         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
+         * </table>
+         *
+         * <p>When the primaries and/or white point are specified in xyY, the Y component
+         * does not need to be specified and is assumed to be 1.0. Only the xy components
+         * are required.</p>
+         *
+         * <p class="note">The ID, areturned by {@link #getId()}, of an object created by
+         * this constructor is always {@link #MIN_ID}.</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
+         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
+         * @param oetf Opto-electronic transfer function, cannot be null
+         * @param eotf Electro-optical transfer function, cannot be null
+         * @param min The minimum valid value in this color space's RGB range
+         * @param max The maximum valid value in this color space's RGB range
+         *
+         * @throws IllegalArgumentException <p>If any of the following conditions is met:</p>
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
+         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
+         *     <li>The OETF is null or the EOTF is null.</li>
+         *     <li>The minimum valid value is >= the maximum valid value.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        public Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(min = 6, max = 9) float[] primaries,
+                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
+                @NonNull DoubleUnaryOperator oetf,
+                @NonNull DoubleUnaryOperator eotf,
+                float min,
+                float max) {
+            this(name, primaries, whitePoint, oetf, eotf, min, max, MIN_ID);
+        }
+
+        /**
+         * <p>Creates a new RGB color space using a specified set of primaries
+         * and a specified white point.</p>
+         *
+         * <p>The primaries and white point can be specified in the CIE xyY space
+         * or in CIE XYZ. The length of the arrays depends on the chosen space:</p>
+         *
+         * <table summary="Parameters length">
+         *     <tr><th>Space</th><th>Primaries length</th><th>White point length</th></tr>
+         *     <tr><td>xyY</td><td>6</td><td>2</td></tr>
+         *     <tr><td>XYZ</td><td>9</td><td>3</td></tr>
+         * </table>
+         *
+         * <p>When the primaries and/or white point are specified in xyY, the Y component
+         * does not need to be specified and is assumed to be 1.0. Only the xy components
+         * are required.</p>
+         *
+         * @param name Name of the color space, cannot be null, its length must be >= 1
+         * @param primaries RGB primaries as an array of 6 (xy) or 9 (XYZ) floats
+         * @param whitePoint Reference white as an array of 2 (xy) or 3 (XYZ) floats
+         * @param oetf Opto-electronic transfer function, cannot be null
+         * @param eotf Electro-optical transfer function, cannot be null
+         * @param min The minimum valid value in this color space's RGB range
+         * @param max The maximum valid value in this color space's RGB range
+         * @param id ID of this color space as an integer between {@link #MIN_ID} and {@link #MAX_ID}
+         *
+         * @throws IllegalArgumentException If any of the following conditions is met:
+         * <ul>
+         *     <li>The name is null or has a length of 0.</li>
+         *     <li>The primaries array is null or has a length that is neither 6 or 9.</li>
+         *     <li>The white point array is null or has a length that is neither 2 or 3.</li>
+         *     <li>The OETF is null or the EOTF is null.</li>
+         *     <li>The minimum valid value is >= the maximum valid value.</li>
+         *     <li>The ID is not between {@link #MIN_ID} and {@link #MAX_ID}.</li>
+         * </ul>
+         *
+         * @see #get(Named)
+         */
+        private Rgb(
+                @NonNull @Size(min = 1) String name,
+                @NonNull @Size(min = 6, max = 9) float[] primaries,
+                @NonNull @Size(min = 2, max = 3) float[] whitePoint,
+                @NonNull DoubleUnaryOperator oetf,
+                @NonNull DoubleUnaryOperator eotf,
+                float min,
+                float max,
+                @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+
+            super(name, Model.RGB, id);
+
+            if (primaries == null || (primaries.length != 6 && primaries.length != 9)) {
+                throw new IllegalArgumentException("The color space's primaries must be " +
+                        "defined as an array of 6 floats in xyY or 9 floats in XYZ");
+            }
+
+            if (whitePoint == null || (whitePoint.length != 2 && whitePoint.length != 3)) {
+                throw new IllegalArgumentException("The color space's white point must be " +
+                        "defined as an array of 2 floats in xyY or 3 float in XYZ");
+            }
+
+            if (oetf == null || eotf == null) {
+                throw new IllegalArgumentException("The transfer functions of a color space " +
+                        "cannot be null");
+            }
+
+            if (min >= max) {
+                throw new IllegalArgumentException("Invalid range: min=" + min + ", max=" + max +
+                        "; min must be strictly < max");
+            }
+
+            mWhitePoint = xyWhitePoint(whitePoint);
+            mPrimaries =  xyPrimaries(primaries);
+
+            mTransform = computeXYZMatrix(mPrimaries, mWhitePoint);
+            mInverseTransform = inverse3x3(mTransform);
+
+            mOetf = oetf;
+            mEotf = eotf;
+
+            mMin = min;
+            mMax = max;
+
+            DoubleUnaryOperator clamp = this::clamp;
+            mClampedOetf = oetf.andThen(clamp);
+            mClampedEotf = clamp.andThen(eotf);
+
+            // A color space is wide-gamut if its area is >90% of NTSC 1953 and
+            // if it entirely contains the Color space definition in xyY
+            mIsWideGamut = isWideGamut(mPrimaries, min, max);
+            mIsSrgb = isSrgb(mPrimaries, mWhitePoint, oetf, eotf, min, max, id);
+        }
+
+        /**
+         * Creates a copy of the specified color space with a new transform.
+         *
+         * @param colorSpace The color space to create a copy of
+         */
+        private Rgb(Rgb colorSpace,
+                @NonNull @Size(9) float[] transform,
+                @NonNull @Size(min = 2, max = 3) float[] whitePoint) {
+            super(colorSpace.getName(), Model.RGB, -1);
+
+            mWhitePoint = xyWhitePoint(whitePoint);
+            mPrimaries = colorSpace.mPrimaries;
+
+            mTransform = transform;
+            mInverseTransform = inverse3x3(transform);
+
+            mMin = colorSpace.mMin;
+            mMax = colorSpace.mMax;
+
+            mOetf = colorSpace.mOetf;
+            mEotf = colorSpace.mEotf;
+
+            mClampedOetf = colorSpace.mClampedOetf;
+            mClampedEotf = colorSpace.mClampedEotf;
+
+            mIsWideGamut = colorSpace.mIsWideGamut;
+            mIsSrgb = colorSpace.mIsSrgb;
+        }
+
+        /**
+         * Copies the non-adapted CIE xyY white point of this color space in
+         * specified array. The Y component is assumed to be 1 and is therefore
+         * not copied into the destination. The x and y components are written
+         * in the array at positions 0 and 1 respectively.
+         *
+         * @param whitePoint The destination array, cannot be null, its length
+         *                   must be >= 2
+         *
+         * @return The destination array passed as a parameter
+         *
+         * @see #getWhitePoint(float[])
+         */
+        @NonNull
+        @Size(min = 2)
+        public float[] getWhitePoint(@NonNull @Size(min = 2) float[] whitePoint) {
+            whitePoint[0] = mWhitePoint[0];
+            whitePoint[1] = mWhitePoint[1];
+            return whitePoint;
+        }
+
+        /**
+         * Returns the non-adapted CIE xyY white point of this color space as
+         * a new array of 2 floats. The Y component is assumed to be 1 and is
+         * therefore not copied into the destination. The x and y components
+         * are written in the array at positions 0 and 1 respectively.
+         *
+         * @return A new non-null array of 2 floats
+         *
+         * @see #getWhitePoint()
+         */
+        @NonNull
+        @Size(2)
+        public float[] getWhitePoint() {
+            return Arrays.copyOf(mWhitePoint, mWhitePoint.length);
+        }
+
+        /**
+         * Copies the primaries of this color space in specified array. The Y
+         * component is assumed to be 1 and is therefore not copied into the
+         * destination. The x and y components of the first primary are written
+         * in the array at positions 0 and 1 respectively.
+         *
+         * @param primaries The destination array, cannot be null, its length
+         *                  must be >= 6
+         *
+         * @return The destination array passed as a parameter
+         *
+         * @see #getPrimaries(float[])
+         */
+        @NonNull
+        @Size(min = 6)
+        public float[] getPrimaries(@NonNull @Size(min = 6) float[] primaries) {
+            System.arraycopy(mPrimaries, 0, primaries, 0, mPrimaries.length);
+            return primaries;
+        }
+
+        /**
+         * Returns the primaries of this color space as a new array of 6 floats.
+         * The Y component is assumed to be 1 and is therefore not copied into
+         * the destination. The x and y components of the first primary are
+         * written in the array at positions 0 and 1 respectively.
+         *
+         * @return A new non-null array of 2 floats
+         *
+         * @see #getWhitePoint()
+         */
+        @NonNull
+        @Size(6)
+        public float[] getPrimaries() {
+            return Arrays.copyOf(mPrimaries, mPrimaries.length);
+        }
+
+        /**
+         * <p>Copies the transform of this color space in specified array. The
+         * transform is used to convert from RGB to XYZ (with the same white
+         * point as this color space). To connect color spaces, you must first
+         * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
+         * same white point.</p>
+         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
+         * to convert between color spaces.</p>
+         *
+         * @param transform The destination array, cannot be null, its length
+         *                  must be >= 9
+         *
+         * @return The destination array passed as a parameter
+         *
+         * @see #getInverseTransform()
+         */
+        @NonNull
+        @Size(min = 9)
+        public float[] getTransform(@NonNull @Size(min = 9) float[] transform) {
+            System.arraycopy(mTransform, 0, transform, 0, mTransform.length);
+            return transform;
+        }
+
+        /**
+         * <p>Returns the transform of this color space as a new array. The
+         * transform is used to convert from RGB to XYZ (with the same white
+         * point as this color space). To connect color spaces, you must first
+         * {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them to the
+         * same white point.</p>
+         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
+         * to convert between color spaces.</p>
+         *
+         * @return A new array of 9 floats
+         *
+         * @see #getInverseTransform(float[])
+         */
+        @NonNull
+        @Size(9)
+        public float[] getTransform() {
+            return Arrays.copyOf(mTransform, mTransform.length);
+        }
+
+        /**
+         * <p>Copies the inverse transform of this color space in specified array.
+         * The inverse transform is used to convert from XYZ to RGB (with the
+         * same white point as this color space). To connect color spaces, you
+         * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
+         * to the same white point.</p>
+         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
+         * to convert between color spaces.</p>
+         *
+         * @param inverseTransform The destination array, cannot be null, its length
+         *                  must be >= 9
+         *
+         * @return The destination array passed as a parameter
+         *
+         * @see #getTransform()
+         */
+        @NonNull
+        @Size(min = 9)
+        public float[] getInverseTransform(@NonNull @Size(min = 9) float[] inverseTransform) {
+            System.arraycopy(mInverseTransform, 0, inverseTransform, 0, mInverseTransform.length);
+            return inverseTransform;
+        }
+
+        /**
+         * <p>Returns the inverse transform of this color space as a new array.
+         * The inverse transform is used to convert from XYZ to RGB (with the
+         * same white point as this color space). To connect color spaces, you
+         * must first {@link ColorSpace#adapt(ColorSpace, float[]) adapt} them
+         * to the same white point.</p>
+         * <p>It is recommended to use {@link ColorSpace#connect(ColorSpace, ColorSpace)}
+         * to convert between color spaces.</p>
+         *
+         * @return A new array of 9 floats
+         *
+         * @see #getTransform(float[])
+         */
+        @NonNull
+        @Size(9)
+        public float[] getInverseTransform() {
+            return Arrays.copyOf(mInverseTransform, mInverseTransform.length);
+        }
+
+        /**
+         * <p>Returns the opto-electronic transfer function (OETF) of this color space.
+         * The inverse function is the electro-optical transfer function (EOTF) returned
+         * by {@link #getEotf()}. These functions are defined to satisfy the following
+         * equality for \(x \in [0..1]\):</p>
+         *
+         * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
+         *
+         * <p>For RGB colors, this function can be used to convert from linear space
+         * to "gamma space" (gamma encoded). The terms gamma space and gamma encoded
+         * are frequently used because many OETFs can be closely approximated using
+         * a simple power function of the form \(x^{\frac{1}{\gamma}}\) (the
+         * approximation of the {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\)
+         * for instance).</p>
+         *
+         * @return A transfer function that converts from linear space to "gamma space"
+         *
+         * @see #getEotf()
+         */
+        @NonNull
+        public DoubleUnaryOperator getOetf() {
+            return mOetf;
+        }
+
+        /**
+         * <p>Returns the electro-optical transfer function (EOTF) of this color space.
+         * The inverse function is the opto-electronic transfer function (OETF)
+         * returned by {@link #getOetf()}. These functions are defined to satisfy the
+         * following equality for \(x \in [0..1]\):</p>
+         *
+         * $$OETF(EOTF(x)) = EOTF(OETF(x)) = x$$
+         *
+         * <p>For RGB colors, this function can be used to convert from "gamma space"
+         * (gamma encoded) to linear space. The terms gamma space and gamma encoded
+         * are frequently used because many EOTFs can be closely approximated using
+         * a simple power function of the form \(x^\gamma\) (the approximation of the
+         * {@link Named#SRGB sRGB} EOTF uses \(\gamma=2.2\) for instance).</p>
+         *
+         * @return A transfer function that converts from "gamma space" to linear space
+         *
+         * @see #getOetf()
+         */
+        @NonNull
+        public DoubleUnaryOperator getEotf() {
+            return mEotf;
+        }
+
+        @Override
+        public boolean isSrgb() {
+            return mIsSrgb;
+        }
+
+        @Override
+        public boolean isWideGamut() {
+            return mIsWideGamut;
+        }
+
+        @Override
+        public float getMinValue(int component) {
+            return mMin;
+        }
+
+        @Override
+        public float getMaxValue(int component) {
+            return mMax;
+        }
+
+        /**
+         * <p>Decodes an RGB value to linear space. This is achieved by
+         * applying this color space's electro-optical transfer function
+         * to the supplied values.</p>
+         *
+         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
+         * more information about transfer functions and their use for
+         * encoding and decoding RGB values.</p>
+         *
+         * @param r The red component to decode to linear space
+         * @param g The green component to decode to linear space
+         * @param b The blue component to decode to linear space
+         * @return A new array of 3 floats containing linear RGB values
+         *
+         * @see #toLinear(float[])
+         * @see #fromLinear(float, float, float)
+         */
+        @NonNull
+        @Size(3)
+        public float[] toLinear(float r, float g, float b) {
+            return toLinear(new float[] { r, g, b });
+        }
+
+        /**
+         * <p>Decodes an RGB value to linear space. This is achieved by
+         * applying this color space's electro-optical transfer function
+         * to the first 3 values of the supplied array. The result is
+         * stored back in the input array.</p>
+         *
+         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
+         * more information about transfer functions and their use for
+         * encoding and decoding RGB values.</p>
+         *
+         * @param v A non-null array of non-linear RGB values, its length
+         *          must be at least 3
+         * @return The specified array
+         *
+         * @see #toLinear(float, float, float)
+         * @see #fromLinear(float[])
+         */
+        @NonNull
+        @Size(min = 3)
+        public float[] toLinear(@NonNull @Size(min = 3) float[] v) {
+            v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
+            v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
+            v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
+            return v;
+        }
+
+        /**
+         * <p>Encodes an RGB value from linear space to this color space's
+         * "gamma space". This is achieved by applying this color space's
+         * opto-electronic transfer function to the supplied values.</p>
+         *
+         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
+         * more information about transfer functions and their use for
+         * encoding and decoding RGB values.</p>
+         *
+         * @param r The red component to encode from linear space
+         * @param g The green component to encode from linear space
+         * @param b The blue component to encode from linear space
+         * @return A new array of 3 floats containing non-linear RGB values
+         *
+         * @see #fromLinear(float[])
+         * @see #toLinear(float, float, float)
+         */
+        @NonNull
+        @Size(3)
+        public float[] fromLinear(float r, float g, float b) {
+            return fromLinear(new float[] { r, g, b });
+        }
+
+        /**
+         * <p>Encodes an RGB value from linear space to this color space's
+         * "gamma space". This is achieved by applying this color space's
+         * opto-electronic transfer function to the first 3 values of the
+         * supplied array. The result is stored back in the input array.</p>
+         *
+         * <p>Refer to the documentation of {@link ColorSpace.Rgb} for
+         * more information about transfer functions and their use for
+         * encoding and decoding RGB values.</p>
+         *
+         * @param v A non-null array of linear RGB values, its length
+         *          must be at least 3
+         * @return A new array of 3 floats containing non-linear RGB values
+         *
+         * @see #fromLinear(float[])
+         * @see #toLinear(float, float, float)
+         */
+        @NonNull
+        @Size(min = 3)
+        public float[] fromLinear(@NonNull @Size(min = 3) float[] v) {
+            v[0] = (float) mClampedOetf.applyAsDouble(v[0]);
+            v[1] = (float) mClampedOetf.applyAsDouble(v[1]);
+            v[2] = (float) mClampedOetf.applyAsDouble(v[2]);
+            return v;
+        }
+
+        @Override
+        @NonNull
+        @Size(min = 3)
+        public float[] toXyz(@NonNull @Size(min = 3) float[] v) {
+            v[0] = (float) mClampedEotf.applyAsDouble(v[0]);
+            v[1] = (float) mClampedEotf.applyAsDouble(v[1]);
+            v[2] = (float) mClampedEotf.applyAsDouble(v[2]);
+            return mul3x3Float3(mTransform, v);
+        }
+
+        @Override
+        @NonNull
+        @Size(min = 3)
+        public float[] fromXyz(@NonNull @Size(min = 3) float[] v) {
+            mul3x3Float3(mInverseTransform, v);
+            v[0] = (float) mClampedOetf.applyAsDouble(v[0]);
+            v[1] = (float) mClampedOetf.applyAsDouble(v[1]);
+            v[2] = (float) mClampedOetf.applyAsDouble(v[2]);
+            return v;
+        }
+
+        private double clamp(double x) {
+            return x < mMin ? mMin : x > mMax ? mMax : x;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            if (!super.equals(o)) return false;
+
+            Rgb rgb = (Rgb) o;
+
+            if (Float.compare(rgb.mMin, mMin) != 0) return false;
+            if (Float.compare(rgb.mMax, mMax) != 0) return false;
+            if (!Arrays.equals(mWhitePoint, rgb.mWhitePoint)) return false;
+            if (!Arrays.equals(mPrimaries, rgb.mPrimaries)) return false;
+            //noinspection SimplifiableIfStatement
+            if (!mOetf.equals(rgb.mOetf)) return false;
+            return mEotf.equals(rgb.mEotf);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = super.hashCode();
+            result = 31 * result + Arrays.hashCode(mWhitePoint);
+            result = 31 * result + Arrays.hashCode(mPrimaries);
+            result = 31 * result + mOetf.hashCode();
+            result = 31 * result + mEotf.hashCode();
+            result = 31 * result + (mMin != +0.0f ? Float.floatToIntBits(mMin) : 0);
+            result = 31 * result + (mMax != +0.0f ? Float.floatToIntBits(mMax) : 0);
+            return result;
+        }
+
+        /**
+         * Computes whether a color space is the sRGB color space or at least
+         * a close approximation.
+         *
+         * @param primaries The set of RGB primaries in xyY as an array of 6 floats
+         * @param whitePoint The white point in xyY as an array of 2 floats
+         * @param OETF The opto-electronic transfer function
+         * @param EOTF The electro-optical transfer function
+         * @param min The minimum value of the color space's range
+         * @param max The minimum value of the color space's range
+         * @param id The ID of the color space
+         * @return True if the color space can be considered as the sRGB color space
+         *
+         * @see #isSrgb()
+         */
+        @SuppressWarnings("RedundantIfStatement")
+        private static boolean isSrgb(
+                @NonNull @Size(6) float[] primaries,
+                @NonNull @Size(2) float[] whitePoint,
+                @NonNull DoubleUnaryOperator OETF,
+                @NonNull DoubleUnaryOperator EOTF,
+                float min,
+                float max,
+                @IntRange(from = MIN_ID, to = MAX_ID) int id) {
+            if (id == 0) return true;
+            if (!compare(primaries, SRGB_PRIMARIES)) {
+                return false;
+            }
+            if (!compare(whitePoint, ILLUMINANT_D65)) {
+                return false;
+            }
+            if (OETF.applyAsDouble(0.5) < 0.5001) return false;
+            if (EOTF.applyAsDouble(0.5) > 0.5001) return false;
+            if (min != 0.0f) return false;
+            if (max != 1.0f) return false;
+            return true;
+        }
+
+        /**
+         * Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form
+         * a wide color gamut. A color gamut is considered wide if its area is &gt; 90%
+         * of the area of NTSC 1953 and if it contains the sRGB color gamut entirely.
+         * If the conditions above are not met, the color space is considered as having
+         * a wide color gamut if its range is larger than [0..1].
+         *
+         * @param primaries RGB primaries in CIE xyY as an array of 6 floats
+         * @param min The minimum value of the color space's range
+         * @param max The minimum value of the color space's range
+         * @return True if the color space has a wide gamut, false otherwise
+         *
+         * @see #isWideGamut()
+         * @see #area(float[])
+         */
+        private static boolean isWideGamut(@NonNull @Size(6) float[] primaries,
+                float min, float max) {
+            return (area(primaries) / area(NTSC_1953_PRIMARIES) > 0.9f &&
+                            contains(primaries, SRGB_PRIMARIES)) || (min < 0.0f && max > 1.0f);
+        }
+
+        /**
+         * Computes the area of the triangle represented by a set of RGB primaries
+         * in the CIE xyY space.
+         *
+         * @param primaries The triangle's vertices, as RGB primaries in an array of 6 floats
+         * @return The area of the triangle
+         *
+         * @see #isWideGamut(float[], float, float)
+         */
+        private static float area(@NonNull @Size(6) float[] primaries) {
+            float Rx = primaries[0];
+            float Ry = primaries[1];
+            float Gx = primaries[2];
+            float Gy = primaries[3];
+            float Bx = primaries[4];
+            float By = primaries[5];
+            float det = Rx * Gy + Ry * Bx + Gx * By - Gy * Bx - Ry * Gx - Rx * By;
+            float r = 0.5f * det;
+            return r < 0.0f ? -r : r;
+        }
+
+        /**
+         * Computes the cross product of two 2D vectors.
+         *
+         * @param ax The x coordinate of the first vector
+         * @param ay The y coordinate of the first vector
+         * @param bx The x coordinate of the second vector
+         * @param by The y coordinate of the second vector
+         * @return The result of a x b
+         */
+        private static float cross(float ax, float ay, float bx, float by) {
+            return ax * by - ay * bx;
+        }
+
+        /**
+         * Decides whether a 2D triangle, identified by the 6 coordinates of its
+         * 3 vertices, is contained within another 2D triangle, also identified
+         * by the 6 coordinates of its 3 vertices.
+         *
+         * In the illustration below, we want to test whether the RGB triangle
+         * is contained within the triangle XYZ formed by the 3 vertices at
+         * the "+" locations.
+         *
+         *                                     Y     .
+         *                                 .   +    .
+         *                                  .     ..
+         *                                   .   .
+         *                                    . .
+         *                                     .  G
+         *                                     *
+         *                                    * *
+         *                                  **   *
+         *                                 *      **
+         *                                *         *
+         *                              **           *
+         *                             *              *
+         *                            *                *
+         *                          **                  *
+         *                         *                     *
+         *                        *                       **
+         *                      **                          *   R    ...
+         *                     *                             *  .....
+         *                    *                         ***** ..
+         *                  **              ************       .   +
+         *              B  *    ************                    .   X
+         *           ......*****                                 .
+         *     ......    .                                        .
+         *             ..
+         *        +   .
+         *      Z    .
+         *
+         * RGB is contained within XYZ if all the following conditions are true
+         * (with "x" the cross product operator):
+         *
+         *   -->  -->
+         *   GR x RX >= 0
+         *   -->  -->
+         *   RX x BR >= 0
+         *   -->  -->
+         *   RG x GY >= 0
+         *   -->  -->
+         *   GY x RG >= 0
+         *   -->  -->
+         *   RB x BZ >= 0
+         *   -->  -->
+         *   BZ x GB >= 0
+         *
+         * @param p1 The enclosing triangle
+         * @param p2 The enclosed triangle
+         * @return True if the triangle p1 contains the triangle p2
+         *
+         * @see #isWideGamut(float[], float, float)
+         */
+        @SuppressWarnings("RedundantIfStatement")
+        private static boolean contains(@NonNull @Size(6) float[] p1, @NonNull @Size(6) float[] p2) {
+            // Translate the vertices p1 in the coordinates system
+            // with the vertices p2 as the origin
+            float[] p0 = new float[] {
+                    p1[0] - p2[0], p1[1] - p2[1],
+                    p1[2] - p2[2], p1[3] - p2[3],
+                    p1[4] - p2[4], p1[5] - p2[5],
+            };
+            // Check the first vertex of p1
+            if (cross(p0[0], p0[1], p2[0] - p2[4], p2[1] - p2[5]) < 0 ||
+                    cross(p2[0] - p2[2], p2[1] - p2[3], p0[0], p0[1]) < 0) {
+                return false;
+            }
+            // Check the second vertex of p1
+            if (cross(p0[2], p0[3], p2[2] - p2[0], p2[3] - p2[1]) < 0 ||
+                    cross(p2[2] - p2[4], p2[3] - p2[5], p0[2], p0[3]) < 0) {
+                return false;
+            }
+            // Check the third vertex of p1
+            if (cross(p0[4], p0[5], p2[4] - p2[2], p2[5] - p2[3]) < 0 ||
+                    cross(p2[4] - p2[0], p2[5] - p2[1], p0[4], p0[5]) < 0) {
+                return false;
+            }
+            return true;
+        }
+
+        /**
+         * Computes the primaries  of a color space identified only by
+         * its RGB->XYZ transform matrix. This method assumes that the
+         * range of the color space is [0..1].
+         *
+         * @param toXYZ The color space's 3x3 transform matrix to XYZ
+         * @param EOTF The color space's electro-optical transfer function
+         * @return A new array of 6 floats containing the color space's
+         *         primaries in CIE xyY
+         */
+        @NonNull
+        @Size(6)
+        private static float[] computePrimaries(@NonNull @Size(9) float[] toXYZ,
+                DoubleUnaryOperator EOTF) {
+            float one = (float) EOTF.applyAsDouble(1.0);
+            float[] r = mul3x3Float3(toXYZ, new float[] { one, 0.0f, 0.0f });
+            float[] g = mul3x3Float3(toXYZ, new float[] { 0.0f, one, 0.0f });
+            float[] b = mul3x3Float3(toXYZ, new float[] { 0.0f, 0.0f, one });
+
+            float rSum = r[0] + r[1] + r[2];
+            float gSum = g[0] + g[1] + g[2];
+            float bSum = b[0] + b[1] + b[2];
+
+            return new float[] {
+                    r[0] / rSum, r[1] / rSum,
+                    g[0] / gSum, g[1] / gSum,
+                    b[0] / bSum, b[1] / bSum,
+            };
+        }
+
+        /**
+         * Computes the white point of a color space identified only by
+         * its RGB->XYZ transform matrix. This method assumes that the
+         * range of the color space is [0..1].
+         *
+         * @param toXYZ The color space's 3x3 transform matrix to XYZ
+         * @param EOTF The color space's electro-optical transfer function
+         * @return A new array of 2 floats containing the color space's
+         *         white point in CIE xyY
+         */
+        @NonNull
+        @Size(2)
+        private static float[] computeWhitePoint(@NonNull @Size(9) float[] toXYZ,
+                @NonNull DoubleUnaryOperator EOTF) {
+            float one = (float) EOTF.applyAsDouble(1.0);
+            float[] w = mul3x3Float3(toXYZ, new float[] { one, one, one });
+            float sum = w[0] + w[1] + w[2];
+            return new float[] { w[0] / sum, w[1] / sum };
+        }
+
+        /**
+         * Converts the specified RGB primaries point to xyY if needed. The primaries
+         * can be specified as an array of 6 floats (in CIE xyY) or 9 floats
+         * (in CIE XYZ). If no conversion is needed, the input array is copied.
+         *
+         * @param primaries The primaries in xyY or XYZ
+         * @return A new array of 6 floats containing the primaries in xyY
+         */
+        @NonNull
+        @Size(6)
+        private static float[] xyPrimaries(@NonNull @Size(min = 6, max = 9) float[] primaries) {
+            float[] xyPrimaries = new float[6];
+
+            // XYZ to xyY
+            if (primaries.length == 9) {
+                float sum;
+
+                sum = primaries[0] + primaries[1] + primaries[2];
+                xyPrimaries[0] = primaries[0] / sum;
+                xyPrimaries[1] = primaries[1] / sum;
+
+                sum = primaries[3] + primaries[4] + primaries[5];
+                xyPrimaries[2] = primaries[3] / sum;
+                xyPrimaries[3] = primaries[4] / sum;
+
+                sum = primaries[6] + primaries[7] + primaries[8];
+                xyPrimaries[4] = primaries[6] / sum;
+                xyPrimaries[5] = primaries[7] / sum;
+            } else {
+                System.arraycopy(primaries, 0, xyPrimaries, 0, 6);
+            }
+
+            return xyPrimaries;
+        }
+
+        /**
+         * Converts the specified white point to xyY if needed. The white point
+         * can be specified as an array of 2 floats (in CIE xyY) or 3 floats
+         * (in CIE XYZ). If no conversion is needed, the input array is copied.
+         *
+         * @param whitePoint The white point in xyY or XYZ
+         * @return A new array of 2 floats containing the white point in xyY
+         */
+        @NonNull
+        @Size(2)
+        private static float[] xyWhitePoint(@Size(min = 2, max = 3) float[] whitePoint) {
+            float[] xyWhitePoint = new float[2];
+
+            // XYZ to xyY
+            if (whitePoint.length == 3) {
+                float sum = whitePoint[0] + whitePoint[1] + whitePoint[2];
+                xyWhitePoint[0] = whitePoint[0] / sum;
+                xyWhitePoint[1] = whitePoint[1] / sum;
+            } else {
+                System.arraycopy(whitePoint, 0, xyWhitePoint, 0, 2);
+            }
+
+            return xyWhitePoint;
+        }
+
+        /**
+         * Computes the matrix that converts from RGB to XYZ based on RGB
+         * primaries and a white point, both specified in the CIE xyY space.
+         * The Y component of the primaries and white point is implied to be 1.
+         *
+         * @param primaries The RGB primaries in xyY, as an array of 6 floats
+         * @param whitePoint The white point in xyY, as an array of 2 floats
+         * @return A 3x3 matrix as a new array of 9 floats
+         */
+        @NonNull
+        @Size(9)
+        private static float[] computeXYZMatrix(
+                @NonNull @Size(6) float[] primaries,
+                @NonNull @Size(2) float[] whitePoint) {
+            float Rx = primaries[0];
+            float Ry = primaries[1];
+            float Gx = primaries[2];
+            float Gy = primaries[3];
+            float Bx = primaries[4];
+            float By = primaries[5];
+            float Wx = whitePoint[0];
+            float Wy = whitePoint[1];
+
+            float oneRxRy = (1 - Rx) / Ry;
+            float oneGxGy = (1 - Gx) / Gy;
+            float oneBxBy = (1 - Bx) / By;
+            float oneWxWy = (1 - Wx) / Wy;
+
+            float RxRy = Rx / Ry;
+            float GxGy = Gx / Gy;
+            float BxBy = Bx / By;
+            float WxWy = Wx / Wy;
+
+            float BY =
+                    ((oneWxWy - oneRxRy) * (GxGy - RxRy) - (WxWy - RxRy) * (oneGxGy - oneRxRy)) /
+                    ((oneBxBy - oneRxRy) * (GxGy - RxRy) - (BxBy - RxRy) * (oneGxGy - oneRxRy));
+            float GY = (WxWy - RxRy - BY * (BxBy - RxRy)) / (GxGy - RxRy);
+            float RY = 1 - GY - BY;
+
+            float RYRy = RY / Ry;
+            float GYGy = GY / Gy;
+            float BYBy = BY / By;
+
+            return new float[] {
+                    RYRy * Rx, RY, RYRy * (1 - Rx - Ry),
+                    GYGy * Gx, GY, GYGy * (1 - Gx - Gy),
+                    BYBy * Bx, BY, BYBy * (1 - Bx - By)
+            };
+        }
+    }
+
+    /**
+     * {@usesMathJax}
+     *
+     * <p>A connector transforms colors from a source color space to a destination
+     * color space.</p>
+     *
+     * <p>A source color space is connected to a destination color space using the
+     * color transform \(C\) computed from their respective transforms noted
+     * \(T_{src}\) and \(T_{dst}\) in the following equation:</p>
+     *
+     * $$C = T^{-1}_{dst} . T_{src}$$
+     *
+     * <p>The transform \(C\) shown above is only valid when the source and
+     * destination color spaces have the same profile connection space (PCS).
+     * We know that instances of {@link ColorSpace} always use CIE XYZ as their
+     * PCS but their white points might differ. When they do, we must perform
+     * a chromatic adaptation of the color spaces' transforms. To do so, we
+     * use the von Kries method described in the documentation of {@link Adaptation},
+     * using the CIE standard illuminant {@link ColorSpace#ILLUMINANT_D50 D50}
+     * as the target white point.</p>
+     *
+     * <p>Example of conversion from {@link Named#SRGB sRGB} to
+     * {@link Named#DCI_P3 DCI-P3}:</p>
+     *
+     * <pre class="prettyprint">
+     * ColorSpace.Connector connector = ColorSpace.connect(
+     *         ColorSpace.get(ColorSpace.Named.SRGB),
+     *         ColorSpace.get(ColorSpace.Named.DCI_P3));
+     * float[] p3 = connector.transform(1.0f, 0.0f, 0.0f);
+     * // p3 contains { 0.9473, 0.2740, 0.2076 }
+     * </pre>
+     *
+     * @see Adaptation
+     * @see ColorSpace#adapt(ColorSpace, float[], Adaptation)
+     * @see ColorSpace#adapt(ColorSpace, float[])
+     * @see ColorSpace#connect(ColorSpace, ColorSpace, RenderIntent)
+     * @see ColorSpace#connect(ColorSpace, ColorSpace)
+     * @see ColorSpace#connect(ColorSpace, RenderIntent)
+     * @see ColorSpace#connect(ColorSpace)
+     */
+    public static class Connector {
+        @NonNull private final ColorSpace mSource;
+        @NonNull private final ColorSpace mDestination;
+        @NonNull private final ColorSpace mTransformSource;
+        @NonNull private final ColorSpace mTransformDestination;
+        @NonNull private final RenderIntent mIntent;
+        @NonNull @Size(3) private final float[] mTransform;
+
+        /**
+         * Creates a new connector between a source and a destination color space.
+         *
+         * @param source The source color space, cannot be null
+         * @param destination The destination color space, cannot be null
+         * @param intent The render intent to use when compressing gamuts
+         */
+        Connector(@NonNull ColorSpace source, @NonNull ColorSpace destination,
+                @NonNull RenderIntent intent) {
+            this(source, destination,
+                    source.getModel() == Model.RGB ? adapt(source, ILLUMINANT_D50_XYZ) : source,
+                    destination.getModel() == Model.RGB ?
+                            adapt(destination, ILLUMINANT_D50_XYZ) : destination,
+                    intent, computeTransform(source, destination, intent));
+        }
+
+        /**
+         * To connect between color spaces, we might need to use adapted transforms.
+         * This should be transparent to the user so this constructor takes the
+         * original source and destinations (returned by the getters), as well as
+         * possibly adapted color spaces used by transform().
+         */
+        private Connector(
+                @NonNull ColorSpace source, @NonNull ColorSpace destination,
+                @NonNull ColorSpace transformSource, @NonNull ColorSpace transformDestination,
+                @NonNull RenderIntent intent, @Nullable @Size(3) float[] transform) {
+            mSource = source;
+            mDestination = destination;
+            mTransformSource = transformSource;
+            mTransformDestination = transformDestination;
+            mIntent = intent;
+            mTransform = transform;
+        }
+
+        /**
+         * Computes an extra transform to apply in XYZ space depending on the
+         * selected rendering intent.
+         */
+        private static float[] computeTransform(@NonNull ColorSpace source,
+                @NonNull ColorSpace destination, @NonNull RenderIntent intent) {
+            if (intent != RenderIntent.ABSOLUTE) return null;
+
+            boolean srcRGB = source.getModel() == Model.RGB;
+            boolean dstRGB = destination.getModel() == Model.RGB;
+
+            if (srcRGB && dstRGB) return null;
+
+            if (srcRGB || dstRGB) {
+                ColorSpace.Rgb rgb = (ColorSpace.Rgb) (srcRGB ? source : destination);
+                float[] srcXYZ = srcRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
+                float[] dstXYZ = dstRGB ? xyYToXyz(rgb.mWhitePoint) : ILLUMINANT_D50_XYZ;
+                return new float[] {
+                        srcXYZ[0] / dstXYZ[0],
+                        srcXYZ[1] / dstXYZ[1],
+                        srcXYZ[2] / dstXYZ[2],
+                };
+            }
+
+            return null;
+        }
+
+        /**
+         * Returns the source color space this connector will convert from.
+         *
+         * @return A non-null instance of {@link ColorSpace}
+         *
+         * @see #getDestination()
+         */
+        @NonNull
+        public ColorSpace getSource() {
+            return mSource;
+        }
+
+        /**
+         * Returns the destination color space this connector will convert to.
+         *
+         * @return A non-null instance of {@link ColorSpace}
+         *
+         * @see #getSource()
+         */
+        @NonNull
+        public ColorSpace getDestination() {
+            return mDestination;
+        }
+
+        /**
+         * Returns the render intent this connector will use when mapping the
+         * source color space to the destination color space.
+         *
+         * @return A non-null {@link RenderIntent}
+         *
+         * @see RenderIntent
+         */
+        public RenderIntent getIntent() {
+            return mIntent;
+        }
+
+        /**
+         * <p>Transforms the specified color from the source color space
+         * to a color in the destination color space. This convenience
+         * method assumes a source color model with 3 components
+         * (typically RGB). To transform from color models with more than
+         * 3 components, such as {@link Model#CMYK CMYK}, use
+         * {@link #transform(float[])} instead.</p>
+         *
+         * @param r The red component of the color to transform
+         * @param g The green component of the color to transform
+         * @param b The blue component of the color to transform
+         * @return A new array of 3 floats containing the specified color
+         *         transformed from the source space to the destination space
+         *
+         * @see #transform(float[])
+         */
+        @NonNull
+        @Size(3)
+        public float[] transform(float r, float g, float b) {
+            return transform(new float[] { r, g, b });
+        }
+
+        /**
+         * <p>Transforms the specified color from the source color space
+         * to a color in the destination color space.</p>
+         *
+         * @param v A non-null array of 3 floats containing the value to transform
+         *            and that will hold the result of the transform
+         * @return The v array passed as a parameter, containing the specified color
+         *         transformed from the source space to the destination space
+         *
+         * @see #transform(float, float, float)
+         */
+        @NonNull
+        @Size(min = 3)
+        public float[] transform(@NonNull @Size(min = 3) float[] v) {
+            float[] xyz = mTransformSource.toXyz(v);
+            if (mTransform != null) {
+                xyz[0] *= mTransform[0];
+                xyz[1] *= mTransform[1];
+                xyz[2] *= mTransform[2];
+            }
+            return mTransformDestination.fromXyz(xyz);
+        }
+
+        /**
+         * Optimized connector for RGB->RGB conversions.
+         */
+        private static class Rgb extends Connector {
+            @NonNull private final ColorSpace.Rgb mSource;
+            @NonNull private final ColorSpace.Rgb mDestination;
+            @NonNull private final float[] mTransform;
+
+            Rgb(@NonNull ColorSpace.Rgb source, @NonNull ColorSpace.Rgb destination,
+                    @NonNull RenderIntent intent) {
+                super(source, destination, source, destination, intent, null);
+                mSource = source;
+                mDestination = destination;
+                mTransform = computeTransform(source, destination, intent);
+            }
+
+            @Override
+            public float[] transform(@NonNull @Size(min = 3) float[] rgb) {
+                rgb[0] = (float) mSource.mClampedEotf.applyAsDouble(rgb[0]);
+                rgb[1] = (float) mSource.mClampedEotf.applyAsDouble(rgb[1]);
+                rgb[2] = (float) mSource.mClampedEotf.applyAsDouble(rgb[2]);
+                mul3x3Float3(mTransform, rgb);
+                rgb[0] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[0]);
+                rgb[1] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[1]);
+                rgb[2] = (float) mDestination.mClampedOetf.applyAsDouble(rgb[2]);
+                return rgb;
+            }
+
+            /**
+             * <p>Computes the color transform that connects two RGB color spaces.</p>
+             *
+             * <p>We can only connect color spaces if they use the same profile
+             * connection space. We assume the connection space is always
+             * CIE XYZ but we maye need to perform a chromatic adaptation to
+             * match the white points. If an adaptation is needed, we use the
+             * CIE standard illuminant D50. The unmatched color space is adapted
+             * using the von Kries transform and the {@link Adaptation#BRADFORD}
+             * matrix.</p>
+             *
+             * @param source The source color space, cannot be null
+             * @param destination The destination color space, cannot be null
+             * @param intent The render intent to use when compressing gamuts
+             * @return An array of 9 floats containing the 3x3 matrix transform
+             */
+            @NonNull
+            @Size(9)
+            private static float[] computeTransform(
+                    @NonNull ColorSpace.Rgb source,
+                    @NonNull ColorSpace.Rgb destination,
+                    @NonNull RenderIntent intent) {
+                if (compare(source.mWhitePoint, destination.mWhitePoint)) {
+                    // RGB->RGB using the PCS of both color spaces since they have the same
+                    return mul3x3(destination.mInverseTransform, source.mTransform);
+                } else {
+                    // RGB->RGB using CIE XYZ D50 as the PCS
+                    float[] transform = source.mTransform;
+                    float[] inverseTransform = destination.mInverseTransform;
+
+                    float[] srcXYZ = xyYToXyz(source.mWhitePoint);
+                    float[] dstXYZ = xyYToXyz(destination.mWhitePoint);
+
+                    if (!compare(source.mWhitePoint, ILLUMINANT_D50)) {
+                        float[] srcAdaptation = chromaticAdaptation(
+                                Adaptation.BRADFORD.mTransform, srcXYZ,
+                                Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
+                        transform = mul3x3(srcAdaptation, source.mTransform);
+                    }
+
+                    if (!compare(destination.mWhitePoint, ILLUMINANT_D50)) {
+                        float[] dstAdaptation = chromaticAdaptation(
+                                Adaptation.BRADFORD.mTransform, dstXYZ,
+                                Arrays.copyOf(ILLUMINANT_D50_XYZ, 3));
+                        inverseTransform = inverse3x3(mul3x3(dstAdaptation, destination.mTransform));
+                    }
+
+                    if (intent == RenderIntent.ABSOLUTE) {
+                        transform = mul3x3Diag(
+                                new float[] {
+                                        srcXYZ[0] / dstXYZ[0],
+                                        srcXYZ[1] / dstXYZ[1],
+                                        srcXYZ[2] / dstXYZ[2],
+                                }, transform);
+                    }
+
+                    return mul3x3(inverseTransform, transform);
+                }
+            }
+        }
+
+        /**
+         * Returns the identity connector for a given color space.
+         *
+         * @param source The source and destination color space
+         * @return A non-null connector that does not perform any transform
+         *
+         * @see ColorSpace#connect(ColorSpace, ColorSpace)
+         */
+        static Connector identity(ColorSpace source) {
+            return new Connector(source, source, RenderIntent.RELATIVE) {
+                @Override
+                public float[] transform(@NonNull @Size(min = 3) float[] v) {
+                    return v;
+                }
+            };
+        }
+    }
+}
diff --git a/libs/androidfw/Android.mk b/libs/androidfw/Android.mk
index ad1ead8..7689256 100644
--- a/libs/androidfw/Android.mk
+++ b/libs/androidfw/Android.mk
@@ -65,8 +65,9 @@
 LOCAL_SRC_FILES:= $(deviceSources)
 LOCAL_C_INCLUDES := \
     system/core/include
-LOCAL_STATIC_LIBRARIES := libziparchive libbase
 LOCAL_SHARED_LIBRARIES := \
+    libziparchive \
+    libbase \
     libbinder \
     liblog \
     libcutils \
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index fdf4d52..eff2499 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -181,6 +181,7 @@
     external/skia/include/private \
     external/skia/src/core \
     external/skia/src/effects \
+    external/skia/src/image \
     external/harfbuzz_ng/src \
     external/freetype/include
 
diff --git a/libs/hwui/TreeInfo.h b/libs/hwui/TreeInfo.h
index 2087fca..749efdd 100644
--- a/libs/hwui/TreeInfo.h
+++ b/libs/hwui/TreeInfo.h
@@ -116,6 +116,9 @@
         bool canDrawThisFrame = true;
     } out;
 
+    // This flag helps to disable projection for receiver nodes that do not have any backward
+    // projected children.
+    bool hasBackwardProjectedNodes = false;
     // TODO: Damage calculations
 };
 
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index 7dcbbd0..da9002d 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -24,8 +24,41 @@
 namespace uirenderer {
 namespace skiapipeline {
 
+void RenderNodeDrawable::drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
+        int nestLevel) {
+    LOG_ALWAYS_FATAL_IF(0 == nestLevel && !displayList.mProjectionReceiver);
+    for (auto& child : displayList.mChildNodes) {
+        const RenderProperties& childProperties = child.getNodeProperties();
+
+        //immediate children cannot be projected on their parent
+        if (childProperties.getProjectBackwards() && nestLevel > 0) {
+            SkAutoCanvasRestore acr2(canvas, true);
+            //Apply recorded matrix, which is a total matrix saved at recording time to avoid
+            //replaying all DL commands.
+            canvas->concat(child.getRecordedMatrix());
+            child.drawContent(canvas);
+        }
+
+        //skip walking sub-nodes if current display list contains a receiver with exception of
+        //level 0, which is a known receiver
+        if (0 == nestLevel || !displayList.containsProjectionReceiver()) {
+            SkAutoCanvasRestore acr(canvas, true);
+            SkMatrix nodeMatrix;
+            mat4 hwuiMatrix(child.getRecordedMatrix());
+            auto childNode = child.getRenderNode();
+            childNode->applyViewPropertyTransforms(hwuiMatrix);
+            hwuiMatrix.copyTo(nodeMatrix);
+            canvas->concat(nodeMatrix);
+            SkiaDisplayList* childDisplayList = static_cast<SkiaDisplayList*>(
+                (const_cast<DisplayList*>(childNode->getDisplayList())));
+            if (childDisplayList) {
+                drawBackwardsProjectedNodes(canvas, *childDisplayList, nestLevel+1);
+            }
+        }
+    }
+}
+
 static void clipOutline(const Outline& outline, SkCanvas* canvas, const SkRect* pendingClip) {
-    SkASSERT(outline.willClip());
     Rect possibleRect;
     float radius;
     LOG_ALWAYS_FATAL_IF(!outline.getAsRoundRect(&possibleRect, &radius),
@@ -74,53 +107,25 @@
     SkiaDisplayList* displayList = (SkiaDisplayList*)renderNode->getDisplayList();
 
     SkAutoCanvasRestore acr(canvas, true);
-
     const RenderProperties& properties = this->getNodeProperties();
-    if (displayList->mIsProjectionReceiver) {
-        // this node is a projection receiver. We will gather the projected nodes as we draw our
-        // children, and then draw them on top of this node's content.
-        std::vector<ProjectedChild> newList;
-        for (auto& child : displayList->mChildNodes) {
-            // our direct children are not supposed to project into us (nodes project to, at the
-            // nearest, their grandparents). So we "delay" the list's activation one level by
-            // passing it into mNextProjectedChildrenTarget rather than mProjectedChildrenTarget.
-            child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
-            child.mNextProjectedChildrenTarget = &newList;
+    //pass this outline to the children that may clip backward projected nodes
+    displayList->mProjectedOutline = displayList->containsProjectionReceiver()
+            ? &properties.getOutline() : nullptr;
+    if (!properties.getProjectBackwards()) {
+        drawContent(canvas);
+        if (mProjectedDisplayList) {
+            acr.restore(); //draw projected children using parent matrix
+            LOG_ALWAYS_FATAL_IF(!mProjectedDisplayList->mProjectedOutline);
+            const bool shouldClip = mProjectedDisplayList->mProjectedOutline->getPath();
+            SkAutoCanvasRestore acr2(canvas, shouldClip);
+            canvas->setMatrix(mProjectedDisplayList->mProjectedReceiverParentMatrix);
+            if (shouldClip) {
+                clipOutline(*mProjectedDisplayList->mProjectedOutline, canvas, nullptr);
+            }
+            drawBackwardsProjectedNodes(canvas, *mProjectedDisplayList);
         }
-        // draw ourselves and our children. As a side effect, this will add projected nodes to
-        // newList.
-        this->drawContent(canvas);
-        bool willClip = properties.getOutline().willClip();
-        if (willClip) {
-            canvas->save();
-            clipOutline(properties.getOutline(), canvas, nullptr);
-        }
-        // draw the collected projected nodes
-        for (auto& projectedChild : newList) {
-            canvas->setMatrix(projectedChild.matrix);
-            projectedChild.node->drawContent(canvas);
-        }
-        if (willClip) {
-            canvas->restore();
-        }
-    } else {
-        if (properties.getProjectBackwards() && mProjectedChildrenTarget) {
-            // We are supposed to project this node, so add it to the list and do not actually draw
-            // yet. It will be drawn by its projection receiver.
-            mProjectedChildrenTarget->push_back({ this, canvas->getTotalMatrix() });
-            return;
-        }
-        for (auto& child : displayList->mChildNodes) {
-            // storing these values in the nodes themselves is a bit ugly; they should "really" be
-            // function parameters, but we have to go through the preexisting draw() method and
-            // therefore cannot add additional parameters to it
-            child.mProjectedChildrenTarget = mNextProjectedChildrenTarget;
-            child.mNextProjectedChildrenTarget = mNextProjectedChildrenTarget;
-        }
-        this->drawContent(canvas);
     }
-    mProjectedChildrenTarget = nullptr;
-    mNextProjectedChildrenTarget = nullptr;
+    displayList->mProjectedOutline = nullptr;
 }
 
 static bool layerNeedsPaint(const LayerProperties& properties,
@@ -148,6 +153,10 @@
     if (mComposeLayer) {
         setViewProperties(properties, canvas, &alphaMultiplier);
     }
+    SkiaDisplayList* displayList = (SkiaDisplayList*)mRenderNode->getDisplayList();
+    if (displayList->containsProjectionReceiver()) {
+        displayList->mProjectedReceiverParentMatrix = canvas->getTotalMatrix();
+    }
 
     //TODO should we let the bound of the drawable do this for us?
     const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.h b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
index a2ffc6c..3eed647 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.h
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.h
@@ -29,6 +29,8 @@
 
 namespace skiapipeline {
 
+class SkiaDisplayList;
+
 /**
  * This drawable wraps a RenderNode and enables it to be recorded into a list
  * of Skia drawing commands.
@@ -36,18 +38,6 @@
 class RenderNodeDrawable : public SkDrawable {
 public:
     /**
-     * This struct contains a pointer to a node that is to be
-     * projected into the drawing order of its closest ancestor
-     * (excluding its parent) that is marked as a projection
-     * receiver. The matrix is used to ensure that the node is
-     * drawn with same matrix as it would have prior to projection.
-     */
-    struct ProjectedChild {
-        const RenderNodeDrawable* node;
-        const SkMatrix matrix;
-    };
-
-    /**
      * Creates a new RenderNodeDrawable backed by a render node.
      *
      * @param node that has to be drawn
@@ -86,6 +76,14 @@
      */
     const SkMatrix& getRecordedMatrix() const { return mRecordedTransform; }
 
+    /**
+     * Sets a pointer to a display list of the parent render node. The display list is used when
+     * drawing backward projected nodes, when this node is a projection receiver.
+     */
+    void setProjectedDisplayList(SkiaDisplayList* projectedDisplayList) {
+        mProjectedDisplayList = projectedDisplayList;
+    }
+
 protected:
     /*
      * Return the (conservative) bounds of what the drawable will draw.
@@ -108,6 +106,16 @@
     sp<RenderNode> mRenderNode;
 
     /**
+     * Walks recursively the display list and draws the content of backward projected nodes.
+     *
+     * @param canvas used to draw the backward projected nodes
+     * @param displayList is a display list that contains a projection receiver
+     * @param nestLevel should be always 0. Used to track how far we are from the receiver.
+     */
+    void drawBackwardsProjectedNodes(SkCanvas* canvas, const SkiaDisplayList& displayList,
+            int nestLevel = 0);
+
+    /**
      * Applies the rendering properties of a view onto a SkCanvas.
      */
     static void setViewProperties(const RenderProperties& properties, SkCanvas* canvas,
@@ -126,19 +134,6 @@
      */
     const bool mComposeLayer;
 
-    /**
-     * List to which we will add any projected children we encounter while walking our descendents.
-     * This pointer is valid only while the node (including its children) is actively being drawn.
-     */
-    std::vector<ProjectedChild>* mProjectedChildrenTarget = nullptr;
-
-    /**
-     * The value to which we should set our children's mProjectedChildrenTarget. We use two pointers
-     * (mProjectedChildrenTarget and mNextProjectedChildrenTarget) because we need to skip over our
-     * parent when looking for a projection receiver.
-     */
-    std::vector<ProjectedChild>* mNextProjectedChildrenTarget = nullptr;
-
     /*
      * True if the render node is in a reordering section
      */
@@ -148,6 +143,11 @@
      *  Draw the content into a canvas, depending on the render node layer type and mComposeLayer.
      */
     void drawContent(SkCanvas* canvas) const;
+
+    /*
+     * display list that is searched for any render nodes with getProjectBackwards==true
+     */
+    SkiaDisplayList* mProjectedDisplayList = nullptr;
 };
 
 }; // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
index 2ad7f74..9db8cd3 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.cpp
@@ -64,16 +64,31 @@
         info.canvasContext.unpinImages();
     }
 
+    bool hasBackwardProjectedNodesHere = false;
+    bool hasBackwardProjectedNodesSubtree= false;
+
     for (auto& child : mChildNodes) {
+        hasBackwardProjectedNodesHere |= child.getNodeProperties().getProjectBackwards();
         RenderNode* childNode = child.getRenderNode();
         Matrix4 mat4(child.getRecordedMatrix());
         info.damageAccumulator->pushTransform(&mat4);
         // TODO: a layer is needed if the canvas is rotated or has a non-rect clip
-        bool childFunctorsNeedLayer = functorsNeedLayer;
-        childFn(childNode, info, childFunctorsNeedLayer);
+        info.hasBackwardProjectedNodes = false;
+        childFn(childNode, info, functorsNeedLayer);
+        hasBackwardProjectedNodesSubtree |= info.hasBackwardProjectedNodes;
         info.damageAccumulator->popTransform();
     }
 
+    //The purpose of next block of code is to reset projected display list if there are no
+    //backward projected nodes. This speeds up drawing, by avoiding an extra walk of the tree
+    if (mProjectionReceiver) {
+        mProjectionReceiver->setProjectedDisplayList(hasBackwardProjectedNodesSubtree ? this : nullptr);
+        info.hasBackwardProjectedNodes = hasBackwardProjectedNodesHere;
+    } else {
+        info.hasBackwardProjectedNodes = hasBackwardProjectedNodesSubtree
+                || hasBackwardProjectedNodesHere;
+    }
+
     bool isDirty = false;
     for (auto& vectorDrawable : mVectorDrawables) {
         // If any vector drawable in the display list needs update, damage the node.
@@ -86,7 +101,7 @@
 }
 
 void SkiaDisplayList::reset(SkRect bounds) {
-    mIsProjectionReceiver = false;
+    mProjectionReceiver = nullptr;
 
     mDrawable->reset(bounds);
 
diff --git a/libs/hwui/pipeline/skia/SkiaDisplayList.h b/libs/hwui/pipeline/skia/SkiaDisplayList.h
index f34b485..ff86fd1 100644
--- a/libs/hwui/pipeline/skia/SkiaDisplayList.h
+++ b/libs/hwui/pipeline/skia/SkiaDisplayList.h
@@ -26,6 +26,9 @@
 
 namespace android {
 namespace uirenderer {
+
+class Outline;
+
 namespace skiapipeline {
 
 /**
@@ -119,6 +122,11 @@
     void updateChildren(std::function<void(RenderNode*)> updateFn) override;
 
     /**
+     *  Returns true if there is a child render node that is a projection receiver.
+     */
+    inline bool containsProjectionReceiver() const { return mProjectionReceiver; }
+
+    /**
      * We use std::deque here because (1) we need to iterate through these
      * elements and (2) mDrawable holds pointers to the elements, so they cannot
      * relocate.
@@ -129,7 +137,22 @@
     std::vector<VectorDrawableRoot*> mVectorDrawables;
     sk_sp<SkLiteDL> mDrawable;
 
-    bool mIsProjectionReceiver = false;
+    //mProjectionReceiver points to a child node (stored in mChildNodes) that is as a projection
+    //receiver. It is set at record time and used at both prepare and draw tree traversals to
+    //make sure backward projected nodes are found and drawn immediately after mProjectionReceiver.
+    RenderNodeDrawable* mProjectionReceiver = nullptr;
+
+    //mProjectedOutline is valid only when render node tree is traversed during the draw pass.
+    //Render nodes that have a child receiver node, will store a pointer to their outline in
+    //mProjectedOutline. Child receiver node will apply the clip before any backward projected
+    //node is drawn.
+    const Outline* mProjectedOutline = nullptr;
+
+    //mProjectedReceiverParentMatrix is valid when render node tree is traversed during the draw
+    //pass. Render nodes that have a child receiver node, will store their matrix in
+    //mProjectedReceiverParentMatrix. Child receiver node will set the matrix and then clip with the
+    //outline of their parent.
+    SkMatrix mProjectedReceiverParentMatrix;
 };
 
 }; // namespace skiapipeline
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
index 621816a..95db258 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.cpp
@@ -32,7 +32,6 @@
 
 void SkiaRecordingCanvas::initDisplayList(uirenderer::RenderNode* renderNode, int width,
         int height) {
-    mBarrierPending = false;
     mCurrentBarrier = nullptr;
     SkASSERT(mDisplayList.get() == nullptr);
 
@@ -76,8 +75,6 @@
 }
 
 void SkiaRecordingCanvas::insertReorderBarrier(bool enableReorder) {
-    mBarrierPending = enableReorder;
-
     if (nullptr != mCurrentBarrier) {
         // finish off the existing chunk
         SkDrawable* drawable =
@@ -86,6 +83,12 @@
         mCurrentBarrier = nullptr;
         drawDrawable(drawable);
     }
+    if (enableReorder) {
+        mCurrentBarrier = (StartReorderBarrierDrawable*)
+                mDisplayList->allocateDrawable<StartReorderBarrierDrawable>(
+                mDisplayList.get());
+        drawDrawable(mCurrentBarrier);
+    }
 }
 
 void SkiaRecordingCanvas::drawLayer(uirenderer::DeferredLayerUpdater* layerUpdater) {
@@ -97,22 +100,14 @@
 }
 
 void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {
-    // lazily create the chunk if needed
-    if (mBarrierPending) {
-        mCurrentBarrier = (StartReorderBarrierDrawable*)
-                mDisplayList->allocateDrawable<StartReorderBarrierDrawable>(
-                mDisplayList.get());
-        drawDrawable(mCurrentBarrier);
-        mBarrierPending = false;
-    }
-
     // record the child node
     mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);
-    drawDrawable(&mDisplayList->mChildNodes.back());
+    auto& renderNodeDrawable = mDisplayList->mChildNodes.back();
+    drawDrawable(&renderNodeDrawable);
 
     // use staging property, since recording on UI thread
     if (renderNode->stagingProperties().isProjectionReceiver()) {
-        mDisplayList->mIsProjectionReceiver = true;
+        mDisplayList->mProjectionReceiver = &renderNodeDrawable;
         // set projectionReceiveIndex so that RenderNode.hasProjectionReceiver returns true
         mDisplayList->projectionReceiveIndex = mDisplayList->mChildNodes.size() - 1;
     }
diff --git a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
index 8aef97f..10829f8 100644
--- a/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
+++ b/libs/hwui/pipeline/skia/SkiaRecordingCanvas.h
@@ -76,7 +76,6 @@
 private:
     SkLiteRecorder mRecorder;
     std::unique_ptr<SkiaDisplayList> mDisplayList;
-    bool mBarrierPending;
     StartReorderBarrierDrawable* mCurrentBarrier;
 
     /**
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index 01046e1..950b2c4 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -1487,6 +1487,8 @@
     *layerHandle = nullptr;
 }
 
+namespace {
+
 static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
     SkPaint paint;
     // order put in blue channel, transparent so overlapped content doesn't get rejected
@@ -1502,15 +1504,30 @@
     node->setPropertyFieldsDirty(RenderNode::TRANSLATION_Z);
     canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
 }
-RENDERTHREAD_TEST(FrameBuilder, zReorder) {
-    class ZReorderTestRenderer : public TestRendererBase {
-    public:
-        void onRectOp(const RectOp& op, const BakedOpState& state) override {
-            int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
-            EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
-        }
-    };
 
+static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder,
+        std::function<void(RenderProperties& props, RecordingCanvas& canvas)> setup) {
+    auto node = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [expectedDrawOrder, setup](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedRect(&canvas, expectedDrawOrder);
+        if (setup) {
+             setup(props, canvas);
+        }
+    });
+    canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
+}
+
+class ZReorderTestRenderer : public TestRendererBase {
+public:
+    void onRectOp(const RectOp& op, const BakedOpState& state) override {
+        int expectedOrder = SkColorGetB(op.paint->getColor()); // extract order from blue channel
+        EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+    }
+};
+
+} // end anonymous namespace
+
+RENDERTHREAD_TEST(FrameBuilder, zReorder) {
     auto parent = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
             [](RenderProperties& props, RecordingCanvas& canvas) {
         drawOrderedNode(&canvas, 0, 10.0f); // in reorder=false at this point, so played inorder
@@ -1525,6 +1542,14 @@
         canvas.insertReorderBarrier(false);
         drawOrderedRect(&canvas, 8);
         drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
+        canvas.insertReorderBarrier(true); //reorder a node ahead of drawrect op
+        drawOrderedRect(&canvas, 11);
+        drawOrderedNode(&canvas, 10, -1.0f);
+        canvas.insertReorderBarrier(false);
+        canvas.insertReorderBarrier(true); //test with two empty reorder sections
+        canvas.insertReorderBarrier(true);
+        canvas.insertReorderBarrier(false);
+        drawOrderedRect(&canvas, 12);
     });
     FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
             sLightGeometry, Caches::getInstance());
@@ -1532,7 +1557,7 @@
 
     ZReorderTestRenderer renderer;
     frameBuilder.replayBakedOps<TestDispatcher>(renderer);
-    EXPECT_EQ(10, renderer.getIndex());
+    EXPECT_EQ(13, renderer.getIndex());
 };
 
 RENDERTHREAD_TEST(FrameBuilder, projectionReorder) {
@@ -2238,5 +2263,349 @@
     EXPECT_EQ(1, renderer.getIndex());
 }
 
+TEST(FrameBuilder, projectionReorderProjectedInMiddle) {
+    /* R is backward projected on B
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderProjectLast) {
+    /* R is backward projected on E
+                  A
+                / | \
+               /  |  \
+              B   C   E
+                  |
+                  R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0,  nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //drawn as 2
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //drawn as 3
+            props.setProjectionReceiver(true);
+        } ); //nodeE
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderNoReceivable) {
+    /* R is backward projected without receiver
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+     auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 255,  [](RenderProperties& props, RecordingCanvas& canvas) {
+                //not having a projection receiver is an undefined behavior
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderParentReceivable) {
+    /* R is backward projected on C
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+     auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderSameNodeReceivable) {
+     auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 255, [](RenderProperties& props, RecordingCanvas& canvas) {
+                //having a node that is projected on itself is an undefined/unexpected behavior
+                props.setProjectionReceiver(true);
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderProjectedSibling) {
+    //TODO: this test together with the next "projectionReorderProjectedSibling2" likely expose a
+    //bug in HWUI. First test draws R, while the second test does not draw R for a nearly identical
+    //tree setup. The correct behaviour is to not draw R, because the receiver cannot be a sibling
+    /* R is backward projected on B. R is not expected to be drawn (see Sibling2 outcome below),
+       but for some reason it is drawn.
+                A
+               /|\
+              / | \
+             B  C  R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+        } ); //nodeC
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+            props.setProjectBackwards(true);
+            props.setClipToBounds(false);
+        } ); //nodeR
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderProjectedSibling2) {
+    /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
+                A
+                |
+                G
+               /|\
+              / | \
+             B  C  R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+                props.setProjectionReceiver(true);
+            } ); //nodeB
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+            } ); //nodeC
+            drawOrderedNode(&canvas, 255, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeG
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderGrandparentReceivable) {
+    /* R is backward projected on B
+                A
+                |
+                B
+                |
+                C
+                |
+                R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) {
+                drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) {
+                    props.setProjectBackwards(true);
+                    props.setClipToBounds(false);
+                } ); //nodeR
+            } ); //nodeC
+        } ); //nodeB
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(3, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderTwoReceivables) {
+    /* B and G are receivables, R is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderTwoReceivablesLikelyScenario) {
+    /* B and G are receivables, G is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(4, renderer.getIndex());
+}
+
+TEST(FrameBuilder, projectionReorderTwoReceivablesDeeper) {
+    /* B and G are receivables, R is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   D
+                    |
+                    R
+    */
+    auto nodeA = TestUtils::createNode<RecordingCanvas>(0, 0, 100, 100,
+            [](RenderProperties& props, RecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, RecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, RecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, RecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 4, [](RenderProperties& props, RecordingCanvas& canvas) { //D
+                drawOrderedNode(&canvas, 3, [](RenderProperties& props, RecordingCanvas& canvas) { //R
+                    props.setProjectBackwards(true);
+                    props.setClipToBounds(false);
+                } ); //nodeR
+            } ); //nodeD
+        } ); //nodeC
+    }); //nodeA
+
+    FrameBuilder frameBuilder(SkRect::MakeWH(100, 100), 100, 100,
+            sLightGeometry, Caches::getInstance());
+    frameBuilder.deferRenderNode(*TestUtils::getSyncedNode(nodeA));
+
+    ZReorderTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(5, renderer.getIndex());
+}
+
 } // namespace uirenderer
 } // namespace android
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 623d971..fafce86 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -21,11 +21,14 @@
 #include "DamageAccumulator.h"
 #include "IContextFactory.h"
 #include "pipeline/skia/SkiaDisplayList.h"
+#include "pipeline/skia/SkiaPipeline.h"
 #include "pipeline/skia/SkiaRecordingCanvas.h"
 #include "renderthread/CanvasContext.h"
 #include "tests/common/TestUtils.h"
 #include "SkiaCanvas.h"
+#include <SkSurface_Base.h>
 #include <SkLiteRecorder.h>
+#include <SkClipStack.h>
 #include <string.h>
 
 
@@ -51,6 +54,8 @@
     ASSERT_EQ(drawable.getRecordedMatrix(), canvas.getTotalMatrix());
 }
 
+namespace {
+
 static void drawOrderedRect(Canvas* canvas, uint8_t expectedDrawOrder) {
     SkPaint paint;
     // order put in blue channel, transparent so overlapped content doesn't get rejected
@@ -67,18 +72,33 @@
     canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
 }
 
-TEST(RenderNodeDrawable, zReorder) {
-    class ZReorderCanvas : public SkCanvas {
-    public:
-        ZReorderCanvas(int width, int height) : SkCanvas(width, height) {}
-        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
-            int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel
-            EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+static void drawOrderedNode(Canvas* canvas, uint8_t expectedDrawOrder,
+        std::function<void(RenderProperties& props, SkiaRecordingCanvas& canvas)> setup) {
+    auto node = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [expectedDrawOrder, setup](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedRect(&canvas, expectedDrawOrder);
+        if (setup) {
+             setup(props, canvas);
         }
-        int getIndex() { return mIndex; }
-    protected:
-        int mIndex = 0;
-    };
+    });
+    canvas->drawRenderNode(node.get()); // canvas takes reference/sole ownership
+}
+
+class ZReorderCanvas : public SkCanvas {
+public:
+    ZReorderCanvas(int width, int height) : SkCanvas(width, height) {}
+    void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+        int expectedOrder = SkColorGetB(paint.getColor()); // extract order from blue channel
+        EXPECT_EQ(expectedOrder, mIndex++) << "An op was drawn out of order";
+    }
+    int getIndex() { return mIndex; }
+protected:
+    int mIndex = 0;
+};
+
+} // end anonymous namespace
+
+TEST(RenderNodeDrawable, zReorder) {
 
     auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
             [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
@@ -94,13 +114,21 @@
         canvas.insertReorderBarrier(false);
         drawOrderedRect(&canvas, 8);
         drawOrderedNode(&canvas, 9, -10.0f); // in reorder=false at this point, so played inorder
+        canvas.insertReorderBarrier(true); //reorder a node ahead of drawrect op
+        drawOrderedRect(&canvas, 11);
+        drawOrderedNode(&canvas, 10, -1.0f);
+        canvas.insertReorderBarrier(false);
+        canvas.insertReorderBarrier(true); //test with two empty reorder sections
+        canvas.insertReorderBarrier(true);
+        canvas.insertReorderBarrier(false);
+        drawOrderedRect(&canvas, 12);
     });
 
     //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
     ZReorderCanvas canvas(100, 100);
     RenderNodeDrawable drawable(parent.get(), &canvas, false);
     canvas.drawDrawable(&drawable);
-    EXPECT_EQ(10, canvas.getIndex());
+    EXPECT_EQ(13, canvas.getIndex());
 }
 
 TEST(RenderNodeDrawable, composeOnLayer)
@@ -136,42 +164,622 @@
     rootNode->setLayerSurface(sk_sp<SkSurface>());
 }
 
-//TODO: refactor to cover test cases from FrameBuilderTests_projectionReorder
-//validate with bounds and projection path mask.
-//TODO: research if we could hook in and mock/validate different aspects of the drawing,
-//instead of validating pixels
-TEST(RenderNodeDrawable, projectDraw) {
-    auto surface = SkSurface::MakeRasterN32Premul(1, 1);
-    SkCanvas& canvas = *surface->getCanvas();
-    canvas.drawColor(SK_ColorBLUE, SkBlendMode::kSrcOver);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorBLUE);
+namespace {
+class ContextFactory : public IContextFactory {
+public:
+    virtual AnimationContext* createAnimationContext(renderthread::TimeLord& clock) override {
+        return new AnimationContext(clock);
+    }
+};
 
-    auto redNode = TestUtils::createSkiaNode(0, 0, 1, 1,
-        [](RenderProperties& props, SkiaRecordingCanvas& redCanvas) {
-            redCanvas.drawColor(SK_ColorRED, SkBlendMode::kSrcOver);
-        }, "redNode");
+inline SkRect getBounds(const SkCanvas* canvas) {
+    SkClipStack::BoundsType boundType;
+    SkRect clipBounds;
+    canvas->getClipStack()->getBounds(&clipBounds, &boundType);
+    return clipBounds;
+}
+inline SkRect getLocalBounds(const SkCanvas* canvas) {
+    SkMatrix invertedTotalMatrix;
+    EXPECT_TRUE(canvas->getTotalMatrix().invert(&invertedTotalMatrix));
+    SkRect outlineInDeviceCoord = getBounds(canvas);
+    SkRect outlineInLocalCoord;
+    invertedTotalMatrix.mapRect(&outlineInLocalCoord, outlineInDeviceCoord);
+    return outlineInLocalCoord;
+}
+} // end anonymous namespace
 
-    auto greenNodeWithRedChild = TestUtils::createSkiaNode(0, 0, 1, 1,
-        [&](RenderProperties& props, SkiaRecordingCanvas& greenCanvasWithRedChild) {
-            greenCanvasWithRedChild.drawRenderNode(redNode.get());
-            greenCanvasWithRedChild.drawColor(SK_ColorGREEN, SkBlendMode::kSrcOver);
-        }, "greenNodeWithRedChild");
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorder) {
+    static const int SCROLL_X = 5;
+    static const int SCROLL_Y = 10;
+    class ProjectionTestCanvas : public SkCanvas {
+    public:
+        ProjectionTestCanvas(int width, int height) : SkCanvas(width, height) {}
+        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+            const int index = mIndex++;
+            SkMatrix expectedMatrix;;
+            switch (index) {
+            case 0:  //this is node "B"
+                EXPECT_EQ(SkRect::MakeWH(100, 100), rect);
+                EXPECT_EQ(SK_ColorWHITE, paint.getColor());
+                expectedMatrix.reset();
+                EXPECT_EQ(SkRect::MakeLTRB(0, 0, 100, 100), getBounds(this));
+                break;
+            case 1:  //this is node "P"
+                EXPECT_EQ(SkRect::MakeLTRB(-10, -10, 60, 60), rect);
+                EXPECT_EQ(SK_ColorDKGRAY, paint.getColor());
+                expectedMatrix.setTranslate(50 - SCROLL_X, 50 - SCROLL_Y);
+                EXPECT_EQ(SkRect::MakeLTRB(-35, -30, 45, 50), getLocalBounds(this));
+                break;
+            case 2:  //this is node "C"
+                EXPECT_EQ(SkRect::MakeWH(100, 50), rect);
+                EXPECT_EQ(SK_ColorBLUE, paint.getColor());
+                expectedMatrix.setTranslate(-SCROLL_X, 50 - SCROLL_Y);
+                EXPECT_EQ(SkRect::MakeLTRB(0, 40, 95, 90), getBounds(this));
+                break;
+            default:
+                ADD_FAILURE();
+            }
+            EXPECT_EQ(expectedMatrix, getTotalMatrix());
+        }
 
-    auto rootNode = TestUtils::createSkiaNode(0, 0, 1, 1,
-        [&](RenderProperties& props, SkiaRecordingCanvas& rootCanvas) {
-            rootCanvas.drawRenderNode(greenNodeWithRedChild.get());
-        }, "rootNode");
-    SkiaDisplayList* rootDisplayList = static_cast<SkiaDisplayList*>(
-        (const_cast<DisplayList*>(rootNode->getDisplayList())));
+        int getIndex() { return mIndex; }
+    protected:
+        int mIndex = 0;
+    };
 
-    RenderNodeDrawable rootDrawable(rootNode.get(), &canvas, false);
-    canvas.drawDrawable(&rootDrawable);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorGREEN);
+    /**
+     * Construct a tree of nodes, where the root (A) has a receiver background (B), and a child (C)
+     * with a projecting child (P) of its own. P would normally draw between B and C's "background"
+     * draw, but because it is projected backwards, it's drawn in between B and C.
+     *
+     * The parent is scrolled by SCROLL_X/SCROLL_Y, but this does not affect the background
+     * (which isn't affected by scroll).
+     */
+    auto receiverBackground = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        properties.setProjectionReceiver(true);
+        // scroll doesn't apply to background, so undone via translationX/Y
+        // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+        properties.setTranslationX(SCROLL_X);
+        properties.setTranslationY(SCROLL_Y);
 
-    //project redNode on rootNode, which will change the test outcome,
-    //because redNode will draw after greenNodeWithRedChild
-    rootDisplayList->mIsProjectionReceiver = true;
-    redNode->animatorProperties().setProjectBackwards(true);
-    canvas.drawDrawable(&rootDrawable);
-    ASSERT_EQ(TestUtils::getColor(surface, 0, 0), SK_ColorRED);
+        SkPaint paint;
+        paint.setColor(SK_ColorWHITE);
+        canvas.drawRect(0, 0, 100, 100, paint);
+    }, "B");
+
+    auto projectingRipple = TestUtils::createSkiaNode(50, 0, 100, 50,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        properties.setProjectBackwards(true);
+        properties.setClipToBounds(false);
+        SkPaint paint;
+        paint.setColor(SK_ColorDKGRAY);
+        canvas.drawRect(-10, -10, 60, 60, paint);
+    }, "P");
+    auto child = TestUtils::createSkiaNode(0, 50, 100, 100,
+            [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        SkPaint paint;
+        paint.setColor(SK_ColorBLUE);
+        canvas.drawRect(0, 0, 100, 50, paint);
+        canvas.drawRenderNode(projectingRipple.get());
+    }, "C");
+    auto parent = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [&receiverBackground, &child](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        // Set a rect outline for the projecting ripple to be masked against.
+        properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f);
+
+        canvas.save(SaveFlags::MatrixClip);
+        canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
+        canvas.drawRenderNode(receiverBackground.get());
+        canvas.drawRenderNode(child.get());
+        canvas.restore();
+    }, "A");
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+            renderThread, false, parent.get(), &contextFactory));
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+    DamageAccumulator damageAccumulator;
+    info.damageAccumulator = &damageAccumulator;
+    info.observer = nullptr;
+    parent->prepareTree(info);
+
+    //parent(A)             -> (receiverBackground, child)
+    //child(C)              -> (rect[0, 0, 100, 50], projectingRipple)
+    //projectingRipple(P)   -> (rect[-10, -10, 60, 60]) -> projects backwards
+    //receiverBackground(B) -> (rect[0, 0, 100, 100]) -> projection receiver
+
+    //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
+    ProjectionTestCanvas canvas(100, 100);
+    RenderNodeDrawable drawable(parent.get(), &canvas, true);
+    canvas.drawDrawable(&drawable);
+    EXPECT_EQ(3, canvas.getIndex());
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionHwLayer) {
+    /* R is backward projected on B and C is a layer.
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+    static const int SCROLL_X = 5;
+    static const int SCROLL_Y = 10;
+    static const int CANVAS_WIDTH = 400;
+    static const int CANVAS_HEIGHT = 400;
+    static const int LAYER_WIDTH = 200;
+    static const int LAYER_HEIGHT = 200;
+    class ProjectionTestCanvas : public SkCanvas {
+    public:
+        ProjectionTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
+        void onDrawArc(const SkRect&, SkScalar startAngle, SkScalar sweepAngle, bool useCenter,
+                const SkPaint&) override {
+            EXPECT_EQ(0, mIndex++); //part of painting the layer
+            EXPECT_EQ(SkRect::MakeLTRB(0, 0, LAYER_WIDTH, LAYER_HEIGHT), getBounds(this));
+        }
+        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_EQ(SkRect::MakeLTRB(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT), getBounds(this));
+        }
+        void onDrawOval(const SkRect&, const SkPaint&) override {
+            EXPECT_EQ(2, mIndex++);
+            SkMatrix expectedMatrix;
+            expectedMatrix.setTranslate(100 - SCROLL_X, 100 - SCROLL_Y);
+            EXPECT_EQ(expectedMatrix, getTotalMatrix());
+            EXPECT_EQ(SkRect::MakeLTRB(-85, -80, 295, 300), getLocalBounds(this));
+        }
+        int mIndex = 0;
+    };
+
+    class ProjectionLayer : public SkSurface_Base {
+    public:
+        ProjectionLayer(ProjectionTestCanvas *canvas)
+            : SkSurface_Base(SkImageInfo::MakeN32Premul(LAYER_WIDTH, LAYER_HEIGHT), nullptr)
+            , mCanvas(canvas) {
+        }
+        void onDraw(SkCanvas*, SkScalar x, SkScalar y, const SkPaint*) override {
+            EXPECT_EQ(3, mCanvas->mIndex++);
+            EXPECT_EQ(SkRect::MakeLTRB(100 - SCROLL_X, 100 - SCROLL_Y, 300 - SCROLL_X,
+                   300 - SCROLL_Y), getBounds(mCanvas));
+        }
+        SkCanvas* onNewCanvas() override {
+            mCanvas->ref();
+            return mCanvas;
+        }
+        sk_sp<SkSurface> onNewSurface(const SkImageInfo&) override {
+            return sk_sp<SkSurface>();
+        }
+        sk_sp<SkImage> onNewImageSnapshot(SkBudgeted, SkCopyPixelsMode) override {
+            return sk_sp<SkImage>();
+        }
+        void onCopyOnWrite(ContentChangeMode) override {}
+        ProjectionTestCanvas* mCanvas;
+    };
+
+    auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        properties.setProjectionReceiver(true);
+        // scroll doesn't apply to background, so undone via translationX/Y
+        // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+        properties.setTranslationX(SCROLL_X);
+        properties.setTranslationY(SCROLL_Y);
+
+        canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
+    }, "B"); //B
+    auto projectingRipple = TestUtils::createSkiaNode(0, 0, LAYER_WIDTH, LAYER_HEIGHT,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        properties.setProjectBackwards(true);
+        properties.setClipToBounds(false);
+        canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds
+    }, "R"); //R
+    auto child = TestUtils::createSkiaNode(100, 100, 300, 300,
+            [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        canvas.drawRenderNode(projectingRipple.get());
+        canvas.drawArc(0, 0, LAYER_WIDTH, LAYER_HEIGHT, 0.0f, 280.0f, true, SkPaint());
+    }, "C"); //C
+    auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+            [&receiverBackground, &child](RenderProperties& properties,
+            SkiaRecordingCanvas& canvas) {
+        // Set a rect outline for the projecting ripple to be masked against.
+        properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f);
+        canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
+        canvas.drawRenderNode(receiverBackground.get());
+        canvas.drawRenderNode(child.get());
+    }, "A"); //A
+
+    //prepareTree is required to find, which receivers have backward projected nodes
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+            renderThread, false, parent.get(), &contextFactory));
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+    DamageAccumulator damageAccumulator;
+    info.damageAccumulator = &damageAccumulator;
+    info.observer = nullptr;
+    parent->prepareTree(info);
+
+    sk_sp<ProjectionTestCanvas> canvas(new ProjectionTestCanvas());
+    //set a layer after prepareTree to avoid layer logic there
+    child->animatorProperties().mutateLayerProperties().setType(LayerType::RenderLayer);
+    sk_sp<SkSurface> surfaceLayer1(new ProjectionLayer(canvas.get()));
+    child->setLayerSurface(surfaceLayer1);
+    Matrix4 windowTransform;
+    windowTransform.loadTranslate(100, 100, 0);
+    child->getSkiaLayer()->inverseTransformInWindow.loadInverse(windowTransform);
+
+    LayerUpdateQueue layerUpdateQueue;
+    layerUpdateQueue.enqueueLayerWithDamage(child.get(),
+            android::uirenderer::Rect(LAYER_WIDTH, LAYER_HEIGHT));
+    SkiaPipeline::renderLayersImpl(layerUpdateQueue, true);
+    EXPECT_EQ(1, canvas->mIndex);  //assert index 0 is drawn on the layer
+
+    RenderNodeDrawable drawable(parent.get(), canvas.get(), true);
+    canvas->drawDrawable(&drawable);
+    EXPECT_EQ(4, canvas->mIndex);
+
+    // clean up layer pointer, so we can safely destruct RenderNode
+    child->setLayerSurface(nullptr);
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionChildScroll) {
+    /* R is backward projected on B.
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+    static const int SCROLL_X = 500000;
+    static const int SCROLL_Y = 0;
+    static const int CANVAS_WIDTH = 400;
+    static const int CANVAS_HEIGHT = 400;
+    class ProjectionChildScrollTestCanvas : public SkCanvas {
+    public:
+        ProjectionChildScrollTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
+        void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_TRUE(getTotalMatrix().isIdentity());
+        }
+        void onDrawOval(const SkRect&, const SkPaint&) override {
+            EXPECT_EQ(1, mIndex++);
+            EXPECT_EQ(SkRect::MakeWH(CANVAS_WIDTH, CANVAS_HEIGHT), getBounds(this));
+            EXPECT_TRUE(getTotalMatrix().isIdentity());
+        }
+        int mIndex = 0;
+    };
+
+    auto receiverBackground = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        properties.setProjectionReceiver(true);
+        canvas.drawRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT, SkPaint());
+    }, "B"); //B
+    auto projectingRipple = TestUtils::createSkiaNode(0, 0, 200, 200,
+            [](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        // scroll doesn't apply to background, so undone via translationX/Y
+        // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+        properties.setTranslationX(SCROLL_X);
+        properties.setTranslationY(SCROLL_Y);
+        properties.setProjectBackwards(true);
+        properties.setClipToBounds(false);
+        canvas.drawOval(0, 0, 200, 200, SkPaint());
+    }, "R"); //R
+    auto child = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+            [&projectingRipple](RenderProperties& properties, SkiaRecordingCanvas& canvas) {
+        // Record time clip will be ignored by projectee
+        canvas.clipRect(100, 100, 300, 300, SkRegion::kIntersect_Op);
+
+        canvas.translate(-SCROLL_X, -SCROLL_Y); // Apply scroll (note: bg undoes this internally)
+        canvas.drawRenderNode(projectingRipple.get());
+    }, "C"); //C
+    auto parent = TestUtils::createSkiaNode(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
+            [&receiverBackground, &child](RenderProperties& properties,
+            SkiaRecordingCanvas& canvas) {
+        canvas.drawRenderNode(receiverBackground.get());
+        canvas.drawRenderNode(child.get());
+    }, "A"); //A
+
+    //prepareTree is required to find, which receivers have backward projected nodes
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+            renderThread, false, parent.get(), &contextFactory));
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+    DamageAccumulator damageAccumulator;
+    info.damageAccumulator = &damageAccumulator;
+    info.observer = nullptr;
+    parent->prepareTree(info);
+
+    sk_sp<ProjectionChildScrollTestCanvas> canvas(new ProjectionChildScrollTestCanvas());
+    RenderNodeDrawable drawable(parent.get(), canvas.get(), true);
+    canvas->drawDrawable(&drawable);
+    EXPECT_EQ(2, canvas->mIndex);
+}
+
+namespace {
+static int drawNode(RenderThread& renderThread, const sp<RenderNode>& renderNode)
+{
+    ContextFactory contextFactory;
+    std::unique_ptr<CanvasContext> canvasContext(CanvasContext::create(
+            renderThread, false, renderNode.get(), &contextFactory));
+    TreeInfo info(TreeInfo::MODE_RT_ONLY, *canvasContext.get());
+    DamageAccumulator damageAccumulator;
+    info.damageAccumulator = &damageAccumulator;
+    info.observer = nullptr;
+    renderNode->prepareTree(info);
+
+    //create a canvas not backed by any device/pixels, but with dimensions to avoid quick rejection
+    ZReorderCanvas canvas(100, 100);
+    RenderNodeDrawable drawable(renderNode.get(), &canvas, false);
+    canvas.drawDrawable(&drawable);
+    return canvas.getIndex();
+}
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedInMiddle) {
+    /* R is backward projected on B
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectLast) {
+    /* R is backward projected on E
+                  A
+                / | \
+               /  |  \
+              B   C   E
+                  |
+                  R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //drawn as 2
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //drawn as 3
+            props.setProjectionReceiver(true);
+        } ); //nodeE
+    }); //nodeA
+    EXPECT_EQ(4, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderNoReceivable) {
+    /* R is backward projected without receiver
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+     auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                //not having a projection receiver is an undefined behavior
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(2, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderParentReceivable) {
+    /* R is backward projected on C
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+     auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderSameNodeReceivable) {
+    /* R is backward projected on R
+                A
+               / \
+              B   C
+                  |
+                  R
+    */
+     auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, nullptr); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                //having a node that is projected on itself is an undefined/unexpected behavior
+                props.setProjectionReceiver(true);
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(2, drawNode(renderThread, nodeA));
+}
+
+//Note: the outcome for this test is different in HWUI
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedSibling) {
+    /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
+                A
+               /|\
+              / | \
+             B  C  R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        } ); //nodeC
+        drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            props.setProjectBackwards(true);
+            props.setClipToBounds(false);
+        } ); //nodeR
+    }); //nodeA
+    EXPECT_EQ(2, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderProjectedSibling2) {
+    /* R is set to project on B, but R is not drawn because projecting on a sibling is not allowed.
+                A
+                |
+                G
+               /|\
+              / | \
+             B  C  R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                props.setProjectionReceiver(true);
+            } ); //nodeB
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            } ); //nodeC
+            drawOrderedNode(&canvas, 255, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeG
+    }); //nodeA
+    EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderGrandparentReceivable) {
+    /* R is backward projected on B
+                A
+                |
+                B
+                |
+                C
+                |
+                R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+            props.setProjectionReceiver(true);
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+                    props.setProjectBackwards(true);
+                    props.setClipToBounds(false);
+                } ); //nodeR
+            } ); //nodeC
+        } ); //nodeB
+    }); //nodeA
+    EXPECT_EQ(3, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivables) {
+    /* B and G are receivables, R is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(4, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivablesLikelyScenario) {
+    /* B and G are receivables, G is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+                props.setProjectBackwards(true);
+                props.setClipToBounds(false);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
+            } ); //nodeR
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(4, drawNode(renderThread, nodeA));
+}
+
+RENDERTHREAD_TEST(RenderNodeDrawable, projectionReorderTwoReceivablesDeeper) {
+    /* B and G are receivables, R is backward projected
+                A
+               / \
+              B   C
+                 / \
+                G   D
+                    |
+                    R
+    */
+    auto nodeA = TestUtils::createSkiaNode(0, 0, 100, 100,
+            [](RenderProperties& props, SkiaRecordingCanvas& canvas) {
+        drawOrderedNode(&canvas, 0, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //B
+            props.setProjectionReceiver(true);
+        } ); //nodeB
+        drawOrderedNode(&canvas, 1, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //C
+            drawOrderedNode(&canvas, 2, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //G
+                props.setProjectionReceiver(true);
+            } ); //nodeG
+            drawOrderedNode(&canvas, 4, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //D
+                drawOrderedNode(&canvas, 3, [](RenderProperties& props, SkiaRecordingCanvas& canvas) { //R
+                    props.setProjectBackwards(true);
+                    props.setClipToBounds(false);
+                } ); //nodeR
+            } ); //nodeD
+        } ); //nodeC
+    }); //nodeA
+    EXPECT_EQ(5, drawNode(renderThread, nodeA));
 }
diff --git a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
index 67fb78a..899758a 100644
--- a/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
+++ b/libs/hwui/tests/unit/SkiaDisplayListTests.cpp
@@ -33,7 +33,7 @@
     SkRect bounds = SkRect::MakeWH(200, 200);
     SkiaDisplayList skiaDL(bounds);
     ASSERT_TRUE(skiaDL.isEmpty());
-    ASSERT_FALSE(skiaDL.mIsProjectionReceiver);
+    ASSERT_FALSE(skiaDL.mProjectionReceiver);
     ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
 }
 
@@ -42,12 +42,13 @@
     SkiaDisplayList skiaDL(bounds);
 
     SkCanvas dummyCanvas;
+    RenderNodeDrawable drawable(nullptr, &dummyCanvas);
     skiaDL.mChildNodes.emplace_back(nullptr, &dummyCanvas);
     skiaDL.mChildFunctors.emplace_back(nullptr, nullptr, &dummyCanvas);
     skiaDL.mMutableImages.push_back(nullptr);
     skiaDL.mVectorDrawables.push_back(nullptr);
     skiaDL.mDrawable->drawAnnotation(bounds, "testAnnotation", nullptr);
-    skiaDL.mIsProjectionReceiver = true;
+    skiaDL.mProjectionReceiver = &drawable;
 
     ASSERT_EQ(skiaDL.mDrawable->getBounds(), bounds);
     ASSERT_FALSE(skiaDL.mChildNodes.empty());
@@ -55,7 +56,7 @@
     ASSERT_FALSE(skiaDL.mMutableImages.empty());
     ASSERT_FALSE(skiaDL.mVectorDrawables.empty());
     ASSERT_FALSE(skiaDL.isEmpty());
-    ASSERT_TRUE(skiaDL.mIsProjectionReceiver);
+    ASSERT_TRUE(skiaDL.mProjectionReceiver);
 
     bounds = SkRect::MakeWH(100, 100);
     skiaDL.reset(bounds);
@@ -66,7 +67,7 @@
     ASSERT_TRUE(skiaDL.mMutableImages.empty());
     ASSERT_TRUE(skiaDL.mVectorDrawables.empty());
     ASSERT_TRUE(skiaDL.isEmpty());
-    ASSERT_FALSE(skiaDL.mIsProjectionReceiver);
+    ASSERT_FALSE(skiaDL.mProjectionReceiver);
 }
 
 TEST(SkiaDisplayList, reuseDisplayList) {
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 89709ee..5440f0f 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -584,6 +584,10 @@
          * @return the same Builder instance.
          */
         public Builder setLegacyStreamType(int streamType) {
+            if (streamType == AudioManager.STREAM_ACCESSIBILITY) {
+                throw new IllegalArgumentException("STREAM_ACCESSIBILITY is not a legacy stream "
+                        + "type that was used for audio playback");
+            }
             return setInternalLegacyStreamType(streamType);
         }
 
@@ -624,12 +628,15 @@
                     mContentType = CONTENT_TYPE_SONIFICATION;
                     break;
                 case AudioSystem.STREAM_TTS:
+                    mContentType = CONTENT_TYPE_SONIFICATION;
+                    break;
+                case AudioSystem.STREAM_ACCESSIBILITY:
                     mContentType = CONTENT_TYPE_SPEECH;
                     break;
                 default:
                     Log.e(TAG, "Invalid stream type " + streamType + " for AudioAttributes");
             }
-            mUsage = usageForLegacyStreamType(streamType);
+            mUsage = usageForStreamType(streamType);
             return this;
         }
 
@@ -842,8 +849,7 @@
         }
     }
 
-    /** @hide */
-    public static int usageForLegacyStreamType(int streamType) {
+    private static int usageForStreamType(int streamType) {
         switch(streamType) {
             case AudioSystem.STREAM_VOICE_CALL:
                 return USAGE_VOICE_COMMUNICATION;
@@ -862,8 +868,9 @@
                 return USAGE_VOICE_COMMUNICATION;
             case AudioSystem.STREAM_DTMF:
                 return USAGE_VOICE_COMMUNICATION_SIGNALLING;
-            case AudioSystem.STREAM_TTS:
+            case AudioSystem.STREAM_ACCESSIBILITY:
                 return USAGE_ASSISTANCE_ACCESSIBILITY;
+            case AudioSystem.STREAM_TTS:
             default:
                 return USAGE_UNKNOWN;
         }
@@ -915,7 +922,6 @@
         switch (aa.getUsage()) {
             case USAGE_MEDIA:
             case USAGE_GAME:
-            case USAGE_ASSISTANCE_ACCESSIBILITY:
             case USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
                 return AudioSystem.STREAM_MUSIC;
             case USAGE_ASSISTANCE_SONIFICATION:
@@ -935,6 +941,8 @@
             case USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
             case USAGE_NOTIFICATION_EVENT:
                 return AudioSystem.STREAM_NOTIFICATION;
+            case USAGE_ASSISTANCE_ACCESSIBILITY:
+                return AudioSystem.STREAM_ACCESSIBILITY;
             case USAGE_UNKNOWN:
                 return fromGetVolumeControlStream ?
                         AudioManager.USE_DEFAULT_STREAM_TYPE : AudioSystem.STREAM_MUSIC;
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index f24bf09..65eadb6 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -335,6 +335,9 @@
     /** @hide Used to identify the volume of audio streams exclusively transmitted through the
      *        speaker (TTS) of the device */
     public static final int STREAM_TTS = AudioSystem.STREAM_TTS;
+    /** Used to identify the volume of audio streams for accessibility prompts */
+    public static final int STREAM_ACCESSIBILITY = AudioSystem.STREAM_ACCESSIBILITY;
+
     /** Number of audio streams */
     /**
      * @deprecated Do not iterate on volume stream type values.
@@ -798,8 +801,8 @@
      * management of audio settings or the main telephony application.
      *
      * @param streamType The stream type to adjust. One of {@link #STREAM_VOICE_CALL},
-     * {@link #STREAM_SYSTEM}, {@link #STREAM_RING}, {@link #STREAM_MUSIC} or
-     * {@link #STREAM_ALARM}
+     * {@link #STREAM_SYSTEM}, {@link #STREAM_RING}, {@link #STREAM_MUSIC},
+     * {@link #STREAM_ALARM} or {@link #STREAM_ACCESSIBILITY}.
      * @param direction The direction to adjust the volume. One of
      *            {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or
      *            {@link #ADJUST_SAME}.
@@ -3192,7 +3195,8 @@
      *            {@link #STREAM_MUSIC},
      *            {@link #STREAM_ALARM},
      *            {@link #STREAM_NOTIFICATION},
-     *            {@link #STREAM_DTMF}.
+     *            {@link #STREAM_DTMF},
+     *            {@link #STREAM_ACCESSIBILITY}.
      *
      * @return The bit-mask "or" of audio output device codes for all enabled devices on this
      *         stream. Zero or more of
@@ -3235,6 +3239,7 @@
         case STREAM_ALARM:
         case STREAM_NOTIFICATION:
         case STREAM_DTMF:
+        case STREAM_ACCESSIBILITY:
             return AudioSystem.getDevicesForStream(streamType);
         default:
             return 0;
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 384be5c..28c7253 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -63,13 +63,15 @@
     /** Used to identify the volume of audio streams exclusively transmitted through the
      *  speaker (TTS) of the device */
     public static final int STREAM_TTS = 9;
+    /** Used to identify the volume of audio streams for accessibility prompts */
+    public static final int STREAM_ACCESSIBILITY = 10;
     /**
      * @deprecated Use {@link #numStreamTypes() instead}
      */
     public static final int NUM_STREAMS = 5;
 
     // Expose only the getter method publicly so we can change it in the future
-    private static final int NUM_STREAM_TYPES = 10;
+    private static final int NUM_STREAM_TYPES = 11;
     public static final int getNumStreamTypes() { return NUM_STREAM_TYPES; }
 
     public static final String[] STREAM_NAMES = new String[] {
@@ -82,7 +84,8 @@
         "STREAM_BLUETOOTH_SCO",
         "STREAM_SYSTEM_ENFORCED",
         "STREAM_DTMF",
-        "STREAM_TTS"
+        "STREAM_TTS",
+        "STREAM_ACCESSIBILITY"
     };
 
     /*
@@ -773,7 +776,8 @@
         7,  // STREAM_BLUETOOTH_SCO
         7,  // STREAM_SYSTEM_ENFORCED
         11, // STREAM_DTMF
-        11  // STREAM_TTS
+        11, // STREAM_TTS
+        11, // STREAM_ACCESSIBILITY
     };
 
     public static String streamToString(int stream) {
diff --git a/media/java/android/media/IVolumeController.aidl b/media/java/android/media/IVolumeController.aidl
index 90ac416..7f37265 100644
--- a/media/java/android/media/IVolumeController.aidl
+++ b/media/java/android/media/IVolumeController.aidl
@@ -32,4 +32,11 @@
     void setLayoutDirection(int layoutDirection);
 
     void dismiss();
+
+    /**
+     * Change the a11y mode.
+     * @param a11yMode one of {@link VolumePolicy#A11Y_MODE_MEDIA_A11Y_VOLUME},
+     *     {@link VolumePolicy#A11Y_MODE_INDEPENDENT_A11Y_VOLUME}
+     */
+    void setA11yMode(int mode);
 }
diff --git a/media/java/android/media/VolumePolicy.java b/media/java/android/media/VolumePolicy.java
index 1d33128..bbcce82 100644
--- a/media/java/android/media/VolumePolicy.java
+++ b/media/java/android/media/VolumePolicy.java
@@ -25,6 +25,17 @@
 public final class VolumePolicy implements Parcelable {
     public static final VolumePolicy DEFAULT = new VolumePolicy(false, false, true, 400);
 
+    /**
+     * Accessibility volume policy where the STREAM_MUSIC volume (i.e. media volume) affects
+     * the STREAM_ACCESSIBILITY volume, and vice-versa.
+     */
+    public static final int A11Y_MODE_MEDIA_A11Y_VOLUME = 0;
+    /**
+     * Accessibility volume policy where the STREAM_ACCESSIBILITY volume is independent from
+     * any other volume.
+     */
+    public static final int A11Y_MODE_INDEPENDENT_A11Y_VOLUME = 1;
+
     /** Allow volume adjustments lower from vibrate to enter ringer mode = silent */
     public final boolean volumeDownToEnterSilent;
 
diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp
index 96c12dd..4de1d00 100644
--- a/media/jni/android_media_MediaExtractor.cpp
+++ b/media/jni/android_media_MediaExtractor.cpp
@@ -607,8 +607,6 @@
 
     gFields.cryptoInfoSetID =
         env->GetMethodID(clazz, "set", "(I[I[I[B[BI)V");
-
-    DataSource::RegisterDefaultSniffers();
 }
 
 static void android_media_MediaExtractor_native_setup(
diff --git a/media/mca/filterfw/jni/jni_gl_environment.cpp b/media/mca/filterfw/jni/jni_gl_environment.cpp
index 096120e..823e88a 100644
--- a/media/mca/filterfw/jni/jni_gl_environment.cpp
+++ b/media/mca/filterfw/jni/jni_gl_environment.cpp
@@ -63,7 +63,8 @@
 };
 
 jboolean Java_android_filterfw_core_GLEnvironment_nativeAllocate(JNIEnv* env, jobject thiz) {
-  return ToJBool(WrapObjectInJava(new GLEnv(), env, thiz, true));
+  std::unique_ptr<GLEnv> glEnv(new GLEnv());
+  return ToJBool(WrapOwnedObjectInJava(std::move(glEnv), env, thiz, true));
 }
 
 jboolean Java_android_filterfw_core_GLEnvironment_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_gl_frame.cpp b/media/mca/filterfw/jni/jni_gl_frame.cpp
index b55bc5d..27b4cd2 100644
--- a/media/mca/filterfw/jni/jni_gl_frame.cpp
+++ b/media/mca/filterfw/jni/jni_gl_frame.cpp
@@ -48,13 +48,11 @@
                                                            jint height) {
   GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
   if (!gl_env_ptr) return JNI_FALSE;
-  GLFrame* frame = new GLFrame(gl_env_ptr);
+  std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
   if (frame->Init(width, height)) {
-    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
-  } else {
-    delete frame;
-    return JNI_FALSE;
+    return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
   }
+  return JNI_FALSE;
 }
 
 jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithTexture(JNIEnv* env,
@@ -65,13 +63,11 @@
                                                                       jint height) {
   GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
   if (!gl_env_ptr) return JNI_FALSE;
-  GLFrame* frame = new GLFrame(gl_env_ptr);
+  std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
   if (frame->InitWithTexture(tex_id, width, height)) {
-    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
-  } else {
-    delete frame;
-    return JNI_FALSE;
+    return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
   }
+  return JNI_FALSE;
 }
 
 jboolean Java_android_filterfw_core_GLFrame_nativeAllocateWithFbo(JNIEnv* env,
@@ -82,13 +78,11 @@
                                                                   jint height) {
   GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
   if (!gl_env_ptr) return JNI_FALSE;
-  GLFrame* frame = new GLFrame(gl_env_ptr);
+  std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
   if (frame->InitWithFbo(fbo_id, width, height)) {
-    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
-  } else {
-    delete frame;
-    return JNI_FALSE;
+    return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
   }
+  return JNI_FALSE;
 }
 
 jboolean Java_android_filterfw_core_GLFrame_nativeAllocateExternal(JNIEnv* env,
@@ -96,13 +90,11 @@
                                                                    jobject gl_env) {
   GLEnv* gl_env_ptr = ConvertFromJava<GLEnv>(env, gl_env);
   if (!gl_env_ptr) return JNI_FALSE;
-  GLFrame* frame = new GLFrame(gl_env_ptr);
+  std::unique_ptr<GLFrame> frame(new GLFrame(gl_env_ptr));
   if (frame->InitWithExternalTexture()) {
-    return ToJBool(WrapObjectInJava(frame, env, thiz, true));
-  } else {
-    delete frame;
-    return JNI_FALSE;
+    return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
   }
+  return JNI_FALSE;
 }
 
 jboolean Java_android_filterfw_core_GLFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_native_frame.cpp b/media/mca/filterfw/jni/jni_native_frame.cpp
index c8f2352..1a11a19 100644
--- a/media/mca/filterfw/jni/jni_native_frame.cpp
+++ b/media/mca/filterfw/jni/jni_native_frame.cpp
@@ -35,7 +35,8 @@
 jboolean Java_android_filterfw_core_NativeFrame_nativeAllocate(JNIEnv* env,
                                                                jobject thiz,
                                                                jint size) {
-  return ToJBool(WrapObjectInJava(new NativeFrame(size), env, thiz, true));
+  std::unique_ptr<NativeFrame> frame(new NativeFrame(size));
+  return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
 }
 
 jboolean Java_android_filterfw_core_NativeFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_native_program.cpp b/media/mca/filterfw/jni/jni_native_program.cpp
index b30b769..1424607 100644
--- a/media/mca/filterfw/jni/jni_native_program.cpp
+++ b/media/mca/filterfw/jni/jni_native_program.cpp
@@ -28,7 +28,8 @@
 using android::filterfw::NativeProgram;
 
 jboolean Java_android_filterfw_core_NativeProgram_allocate(JNIEnv* env, jobject thiz) {
-  return ToJBool(WrapObjectInJava(new NativeProgram(), env, thiz, true));
+  std::unique_ptr<NativeProgram> program(new NativeProgram());
+  return ToJBool(WrapOwnedObjectInJava(std::move(program), env, thiz, true));
 }
 
 jboolean Java_android_filterfw_core_NativeProgram_deallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_shader_program.cpp b/media/mca/filterfw/jni/jni_shader_program.cpp
index 19f43cd..98be04c 100644
--- a/media/mca/filterfw/jni/jni_shader_program.cpp
+++ b/media/mca/filterfw/jni/jni_shader_program.cpp
@@ -46,21 +46,14 @@
   // Create the shader
   if (!fragment_shader || !gl_env_ptr)
     return false;
-  else if (!vertex_shader)
-    return ToJBool(WrapObjectInJava(new ShaderProgram(
-      gl_env_ptr,
-      ToCppString(env, fragment_shader)),
-      env,
-      thiz,
-      true));
+
+  std::unique_ptr<ShaderProgram> shader;
+  if (!vertex_shader)
+    shader.reset(new ShaderProgram(gl_env_ptr, ToCppString(env, fragment_shader)));
   else
-    return ToJBool(WrapObjectInJava(new ShaderProgram(
-      gl_env_ptr,
-      ToCppString(env, vertex_shader),
-      ToCppString(env, fragment_shader)),
-      env,
-      thiz,
-      true));
+    shader.reset(new ShaderProgram(gl_env_ptr, ToCppString(env, vertex_shader),
+                                   ToCppString(env, fragment_shader)));
+  return ToJBool(WrapOwnedObjectInJava(std::move(shader), env, thiz, true));
 }
 
 jboolean Java_android_filterfw_core_ShaderProgram_deallocate(JNIEnv* env, jobject thiz) {
diff --git a/media/mca/filterfw/jni/jni_util.h b/media/mca/filterfw/jni/jni_util.h
index 11c0871..803ed29 100644
--- a/media/mca/filterfw/jni/jni_util.h
+++ b/media/mca/filterfw/jni/jni_util.h
@@ -16,6 +16,7 @@
 
 #include <jni.h>
 
+#include <memory>
 #include <unordered_map>
 #include <string>
 
@@ -214,6 +215,17 @@
   return pool ? pool->WrapObject(c_object, env, j_object, owns) : false;
 }
 
+// Calls WrapObjectInJava, safely freeing c_object if object creation fails.
+template<typename T>
+bool WrapOwnedObjectInJava(std::unique_ptr<T> c_object, JNIEnv* env,
+                           jobject j_object, bool owns) {
+  if (!WrapObjectInJava<T>(c_object.get(), env, j_object, owns))
+    return false;
+  // If we succeeded, a Java object now owns our c object; don't free it.
+  c_object.release();
+  return true;
+}
+
 // Creates a new Java instance, which wraps the passed C++ instance. Returns
 // the wrapped object or JNI_NULL if there was an error. Pass true to owns, if
 // the Java layer should own the object.
diff --git a/media/mca/filterfw/jni/jni_vertex_frame.cpp b/media/mca/filterfw/jni/jni_vertex_frame.cpp
index caae938..d0439fe 100644
--- a/media/mca/filterfw/jni/jni_vertex_frame.cpp
+++ b/media/mca/filterfw/jni/jni_vertex_frame.cpp
@@ -24,7 +24,8 @@
 jboolean Java_android_filterfw_core_VertexFrame_nativeAllocate(JNIEnv* env,
                                                                jobject thiz,
                                                                jint size) {
-  return ToJBool(WrapObjectInJava(new VertexFrame(size), env, thiz, true));
+  std::unique_ptr<VertexFrame> frame(new VertexFrame(size));
+  return ToJBool(WrapOwnedObjectInJava(std::move(frame), env, thiz, true));
 }
 
 jboolean Java_android_filterfw_core_VertexFrame_nativeDeallocate(JNIEnv* env, jobject thiz) {
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 6a2949a..0a7bdbf 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -26,6 +26,7 @@
 import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
 import static android.os.BatteryManager.EXTRA_PLUGGED;
 import static android.os.BatteryManager.EXTRA_STATUS;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 
 import android.app.ActivityManager;
 import android.app.AlarmManager;
@@ -191,8 +192,6 @@
     // Password attempts
     private SparseIntArray mFailedAttempts = new SparseIntArray();
 
-    /** Tracks whether strong authentication hasn't been used since quite some time per user. */
-    private ArraySet<Integer> mStrongAuthNotTimedOut = new ArraySet<>();
     private final StrongAuthTracker mStrongAuthTracker;
 
     private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
@@ -209,6 +208,7 @@
     private TrustManager mTrustManager;
     private UserManager mUserManager;
     private int mFingerprintRunningState = FINGERPRINT_STATE_STOPPED;
+    private LockPatternUtils mLockPatternUtils;
 
     private final Handler mHandler = new Handler() {
         @Override
@@ -576,8 +576,7 @@
     }
 
     public boolean isUnlockingWithFingerprintAllowed() {
-        return mStrongAuthTracker.isUnlockingWithFingerprintAllowed()
-                && !hasFingerprintUnlockTimedOut(sCurrentUser);
+        return mStrongAuthTracker.isUnlockingWithFingerprintAllowed();
     }
 
     public boolean needsSlowUnlockTransition() {
@@ -588,16 +587,7 @@
         return mStrongAuthTracker;
     }
 
-    /**
-     * @return true if the user hasn't use strong authentication (pattern, PIN, password) since a
-     *         while and thus can't unlock with fingerprint, false otherwise
-     */
-    public boolean hasFingerprintUnlockTimedOut(int userId) {
-        return !mStrongAuthNotTimedOut.contains(userId);
-    }
-
     public void reportSuccessfulStrongAuthUnlockAttempt() {
-        mStrongAuthNotTimedOut.add(sCurrentUser);
         scheduleStrongAuthTimeout();
         if (mFpm != null) {
             byte[] token = null; /* TODO: pass real auth token once fp HAL supports it */
@@ -738,7 +728,7 @@
         public void onReceive(Context context, Intent intent) {
             if (ACTION_STRONG_AUTH_TIMEOUT.equals(intent.getAction())) {
                 int userId = intent.getIntExtra(USER_ID, -1);
-                mStrongAuthNotTimedOut.remove(userId);
+                mLockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_TIMEOUT, userId);
                 notifyStrongAuthStateChanged(userId);
             }
         }
@@ -1110,9 +1100,12 @@
                 PERMISSION_SELF, null /* handler */);
         mTrustManager = (TrustManager) context.getSystemService(Context.TRUST_SERVICE);
         mTrustManager.registerTrustListener(this);
-        new LockPatternUtils(context).registerStrongAuthTracker(mStrongAuthTracker);
+        mLockPatternUtils = new LockPatternUtils(context);
+        mLockPatternUtils.registerStrongAuthTracker(mStrongAuthTracker);
 
-        mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            mFpm = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
+        }
         updateFingerprintListeningState();
         if (mFpm != null) {
             mFpm.addLockoutResetCallback(mLockoutResetCallback);
@@ -1837,7 +1830,6 @@
             pw.println("    disabled(DPM)=" + isFingerprintDisabled(userId));
             pw.println("    possible=" + isUnlockWithFingerprintPossible(userId));
             pw.println("    strongAuthFlags=" + Integer.toHexString(strongAuthFlags));
-            pw.println("    timedout=" + hasFingerprintUnlockTimedOut(userId));
             pw.println("    trustManaged=" + getUserTrustIsManaged(userId));
         }
     }
diff --git a/packages/SettingsLib/res/values-hy-rAM/arrays.xml b/packages/SettingsLib/res/values-hy-rAM/arrays.xml
index 44cfe92..1241bee 100644
--- a/packages/SettingsLib/res/values-hy-rAM/arrays.xml
+++ b/packages/SettingsLib/res/values-hy-rAM/arrays.xml
@@ -25,7 +25,7 @@
     <item msgid="8934131797783724664">"Սկանավորում…"</item>
     <item msgid="8513729475867537913">"Միանում է..."</item>
     <item msgid="515055375277271756">"Նույնականացում…"</item>
-    <item msgid="1943354004029184381">"IP հասցեն գտնվում է...."</item>
+    <item msgid="1943354004029184381">"IP հասցեի ստացում…"</item>
     <item msgid="4221763391123233270">"Միացված է"</item>
     <item msgid="624838831631122137">"Կասեցված է"</item>
     <item msgid="7979680559596111948">"Անջատվում է…"</item>
@@ -43,7 +43,7 @@
     <item msgid="8937994881315223448">"Միացված է <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-ին"</item>
     <item msgid="1330262655415760617">"Անջատված"</item>
     <item msgid="7698638434317271902">"Անջատվում է <xliff:g id="NETWORK_NAME">%1$s</xliff:g>-ից…"</item>
-    <item msgid="197508606402264311">"Անջատված"</item>
+    <item msgid="197508606402264311">"Անջատած է"</item>
     <item msgid="8578370891960825148">"Անհաջող"</item>
     <item msgid="5660739516542454527">"Արգելափակված"</item>
     <item msgid="1805837518286731242">"Վատ ցանցից ժամանակավոր խուսափում"</item>
diff --git a/packages/SettingsLib/res/values-kn-rIN/strings.xml b/packages/SettingsLib/res/values-kn-rIN/strings.xml
index cf8f382..20d8ae9 100644
--- a/packages/SettingsLib/res/values-kn-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-kn-rIN/strings.xml
@@ -155,7 +155,7 @@
     <string name="keep_screen_on" msgid="1146389631208760344">"ಎಚ್ಚರವಾಗಿರು"</string>
     <string name="keep_screen_on_summary" msgid="2173114350754293009">"ಚಾರ್ಜ್ ಮಾಡುವಾಗ ಪರದೆಯು ಎಂದಿಗೂ ನಿದ್ರಾವಸ್ಥೆಗೆ ಹೋಗುವುದಿಲ್ಲ"</string>
     <string name="bt_hci_snoop_log" msgid="3340699311158865670">"ಬ್ಲೂಟೂತ್‌‌ HCI ಸ್ನೂಪ್‌ಲಾಗ್"</string>
-    <string name="bt_hci_snoop_log_summary" msgid="730247028210113851">"ಫೈಲ್‌ನಲ್ಲಿ ಎಲ್ಲ bluetooth HCI ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ಸೆರೆಹಿಡಿಯಿರಿ"</string>
+    <string name="bt_hci_snoop_log_summary" msgid="730247028210113851">"ಫೈಲ್‌ನಲ್ಲಿ ಎಲ್ಲ ಬ್ಲೂಟೂತ್ HCI ಪ್ಯಾಕೆಟ್‌ಗಳನ್ನು ಸೆರೆಹಿಡಿಯಿರಿ"</string>
     <string name="oem_unlock_enable" msgid="6040763321967327691">"OEM ಅನ್‌ಲಾಕ್‌ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="oem_unlock_enable_summary" msgid="4720281828891618376">"ಬೂಟ್‌ಲೋಡರ್‌ ಅನ್‌ಲಾಕ್‌ ಮಾಡಲು ಅನುಮತಿಸಿ"</string>
     <string name="confirm_enable_oem_unlock_title" msgid="4802157344812385674">"OEM ಅನ್‌ಲಾಕ್‌ ಮಾಡುವಿಕೆಯನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
index b16cd08..c7efb07 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/Tile.java
@@ -79,6 +79,11 @@
      */
     public Bundle metaData;
 
+    /**
+     * Optional key to use for this tile.
+     */
+    public String key;
+
     public Tile() {
         // Empty
     }
@@ -113,6 +118,7 @@
         dest.writeString(category);
         dest.writeInt(priority);
         dest.writeBundle(metaData);
+        dest.writeString(key);
     }
 
     public void readFromParcel(Parcel in) {
@@ -132,6 +138,7 @@
         category = in.readString();
         priority = in.readInt();
         metaData = in.readBundle();
+        key = in.readString();
     }
 
     Tile(Parcel in) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
index 9442458..d12e8c0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java
@@ -102,6 +102,12 @@
 
     /**
      * Name of the meta-data item that should be set in the AndroidManifest.xml
+     * to specify the key that should be used for the preference.
+     */
+    public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";
+
+    /**
+     * Name of the meta-data item that should be set in the AndroidManifest.xml
      * to specify the icon that should be displayed for the preference.
      */
     public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
@@ -292,6 +298,7 @@
             int icon = 0;
             CharSequence title = null;
             String summary = null;
+            String keyHint = null;
 
             // Get the activity's meta-data
             try {
@@ -317,6 +324,13 @@
                             summary = metaData.getString(META_DATA_PREFERENCE_SUMMARY);
                         }
                     }
+                    if (metaData.containsKey(META_DATA_PREFERENCE_KEYHINT)) {
+                        if (metaData.get(META_DATA_PREFERENCE_KEYHINT) instanceof Integer) {
+                            keyHint = res.getString(metaData.getInt(META_DATA_PREFERENCE_KEYHINT));
+                        } else {
+                            keyHint = metaData.getString(META_DATA_PREFERENCE_KEYHINT);
+                        }
+                    }
                 }
             } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
                 if (DEBUG) Log.d(LOG_TAG, "Couldn't find info", e);
@@ -338,6 +352,8 @@
             // Replace the intent with this specific activity
             tile.intent = new Intent().setClassName(activityInfo.packageName,
                     activityInfo.name);
+            // Suggest a key for this tile
+            tile.key = keyHint;
 
             return true;
         }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
new file mode 100644
index 0000000..86b210a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.drawer;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Pair;
+
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.when;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class TileUtilsTest {
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private Resources mResources;
+
+    @Before
+    public void setUp() throws NameNotFoundException {
+        MockitoAnnotations.initMocks(this);
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mPackageManager.getResourcesForApplication(anyString())).thenReturn(mResources);
+    }
+
+    @Test
+    public void getTilesForIntent_shouldParseCategory() {
+        final String testCategory = "category1";
+        Intent intent = new Intent();
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        info.add(newInfo(true, testCategory));
+
+        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+                null /* defaultCategory */, outTiles, false /* usePriority */,
+                false /* checkCategory */);
+
+        assertThat(outTiles.size()).isEqualTo(1);
+        assertThat(outTiles.get(0).category).isEqualTo(testCategory);
+    }
+
+    @Test
+    public void getTilesForIntent_shouldParseKeyHintForSystemApp() {
+        String keyHint = "key";
+        Intent intent = new Intent();
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        ResolveInfo resolveInfo = newInfo(true, null /* category */, keyHint);
+        info.add(resolveInfo);
+
+        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+                null /* defaultCategory */, outTiles, false /* usePriority */,
+                false /* checkCategory */);
+
+        assertThat(outTiles.size()).isEqualTo(1);
+        assertThat(outTiles.get(0).key).isEqualTo(keyHint);
+    }
+
+    @Test
+    public void getTilesForIntent_shouldSkipNonSystemApp() {
+        final String testCategory = "category1";
+        Intent intent = new Intent();
+        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
+        List<Tile> outTiles = new ArrayList<>();
+        List<ResolveInfo> info = new ArrayList<>();
+        info.add(newInfo(false, testCategory));
+
+        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
+                .thenReturn(info);
+
+        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
+                null /* defaultCategory */, outTiles, false /* usePriority */,
+                false /* checkCategory */);
+
+        assertThat(outTiles.isEmpty()).isTrue();
+    }
+
+
+    private ResolveInfo newInfo(boolean systemApp, String category) {
+        return newInfo(systemApp, category, null);
+    }
+
+    private ResolveInfo newInfo(boolean systemApp, String category, String keyHint) {
+        ResolveInfo info = new ResolveInfo();
+        info.system = systemApp;
+        info.activityInfo = new ActivityInfo();
+        info.activityInfo.packageName = "abc";
+        info.activityInfo.name = "123";
+        info.activityInfo.metaData = new Bundle();
+        info.activityInfo.metaData.putString("com.android.settings.category", category);
+        if (keyHint != null) {
+            info.activityInfo.metaData.putString("com.android.settings.keyhint", keyHint);
+        }
+        info.activityInfo.applicationInfo = new ApplicationInfo();
+        if (systemApp) {
+            info.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
+        }
+        return info;
+    }
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 0b5383a..d232e00 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -297,7 +297,7 @@
                   android:screenOrientation="behind"
                   android:resizeableActivity="true"
                   android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:theme="@style/RecentsTheme.Wallpaper">
+                  android:theme="@style/RecentsTheme.Grid">
             <intent-filter>
                 <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
             </intent-filter>
diff --git a/packages/SystemUI/res/layout-sw600dp/recents_grid.xml b/packages/SystemUI/res/layout-sw600dp/recents_grid.xml
index cff770a..1a054a5 100644
--- a/packages/SystemUI/res/layout-sw600dp/recents_grid.xml
+++ b/packages/SystemUI/res/layout-sw600dp/recents_grid.xml
@@ -13,22 +13,11 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingTop="24dp"
-    android:paddingBottom="54dp"
-    android:orientation="vertical"
-    android:id="@+id/recents_container"
+    android:id="@+id/recents_view"
+    android:gravity="center"
     android:background="#99000000">
     <include layout="@layout/recents_stack_action_button" />
-    <FrameLayout
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:id="@+id/recents_view"
-        android:layout_marginLeft="12dp"
-        android:layout_marginTop="10dp"
-        android:layout_marginRight="12dp"
-        android:gravity="center">
-    </FrameLayout>
-</LinearLayout>
\ No newline at end of file
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml
index c5b4e84..6732e6c 100644
--- a/packages/SystemUI/res/layout/notification_icon_area.xml
+++ b/packages/SystemUI/res/layout/notification_icon_area.xml
@@ -19,13 +19,7 @@
     android:id="@+id/notification_icon_area_inner"
     android:layout_width="match_parent"
     android:layout_height="match_parent" >
-    <com.android.systemui.statusbar.StatusBarIconView
-        android:id="@+id/moreIcon"
-        android:layout_width="@dimen/status_bar_icon_size"
-        android:layout_height="match_parent"
-        android:src="@drawable/stat_notify_more"
-        android:visibility="gone" />
-    <com.android.systemui.statusbar.phone.IconMerger
+    <com.android.systemui.statusbar.phone.NotificationIconContainer
         android:id="@+id/notificationIcons"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/packages/SystemUI/res/layout/recents_stack_action_button.xml b/packages/SystemUI/res/layout/recents_stack_action_button.xml
index 541000b..34b4e17 100644
--- a/packages/SystemUI/res/layout/recents_stack_action_button.xml
+++ b/packages/SystemUI/res/layout/recents_stack_action_button.xml
@@ -33,4 +33,5 @@
     android:fontFamily="sans-serif-medium"
     android:background="@drawable/recents_stack_action_background"
     android:visibility="invisible"
-    android:forceHasOverlappingRendering="false" />
+    android:forceHasOverlappingRendering="false"
+    style="?attr/clearAllStyle" />
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index 63af3e0..6784254 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -42,7 +42,7 @@
     <LinearLayout android:id="@+id/status_bar_contents"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:paddingStart="6dp"
+        android:paddingStart="@dimen/status_bar_padding_start"
         android:paddingEnd="8dp"
         android:orientation="horizontal"
         >
diff --git a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml b/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
deleted file mode 100644
index 7df6bc6..0000000
--- a/packages/SystemUI/res/layout/status_bar_notification_keyguard_overflow.xml
+++ /dev/null
@@ -1,74 +0,0 @@
-<!--
-  ~ Copyright (C) 2014 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
-  -->
-
-<!-- Extends FrameLayout -->
-<com.android.systemui.statusbar.NotificationOverflowContainer
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/notification_summary_height"
-    android:focusable="true"
-    android:clickable="true"
-    >
-
-    <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundNormal"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        />
-    <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundDimmed"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        />
-
-    <com.android.keyguard.AlphaOptimizedLinearLayout
-        android:id="@+id/content"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
-        <TextView
-            android:id="@+id/more_text"
-            android:layout_width="32dp"
-            android:layout_height="32dp"
-            android:layout_marginStart="16dp"
-            android:layout_marginEnd="12dp"
-            android:layout_gravity="center_vertical"
-            android:background="@drawable/keyguard_overflow_number_background"
-            android:gravity="center"
-            android:textColor="#ff686868"
-            android:textStyle="bold"
-            android:textSize="14dp"
-            />
-        <com.android.systemui.statusbar.StatusBarIconView
-            android:id="@+id/more_icon_overflow"
-            android:layout_width="@dimen/status_bar_icon_size"
-            android:layout_height="match_parent"
-            android:src="@drawable/stat_notify_more"
-            android:tint="@color/keyguard_overflow_content_color"
-            android:visibility="gone"
-            />
-        <com.android.systemui.statusbar.NotificationOverflowIconsView
-            android:id="@+id/overflow_icons_view"
-            android:layout_gravity="center_vertical"
-            android:layout_marginEnd="8dp"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            />
-    </com.android.keyguard.AlphaOptimizedLinearLayout>
-
-    <com.android.systemui.statusbar.notification.FakeShadowView
-        android:id="@+id/fake_shadow"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-
-</com.android.systemui.statusbar.NotificationOverflowContainer>
diff --git a/packages/SystemUI/res/layout/status_bar_notification_shelf.xml b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
new file mode 100644
index 0000000..088deba
--- /dev/null
+++ b/packages/SystemUI/res/layout/status_bar_notification_shelf.xml
@@ -0,0 +1,47 @@
+<!--
+  ~ 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
+  -->
+
+<!-- Extends FrameLayout -->
+<com.android.systemui.statusbar.NotificationShelf
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/notification_shelf_height"
+    android:focusable="true"
+    android:clickable="true"
+    >
+
+    <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundNormal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        />
+    <com.android.systemui.statusbar.NotificationBackgroundView android:id="@+id/backgroundDimmed"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        />
+    <com.android.systemui.statusbar.phone.NotificationIconContainer
+        android:id="@+id/content"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:paddingStart="13dp"
+        android:paddingEnd="13dp"
+        android:gravity="center_vertical" />
+
+    <com.android.systemui.statusbar.notification.FakeShadowView
+        android:id="@+id/fake_shadow"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</com.android.systemui.statusbar.NotificationShelf>
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index ad65f82..a229866 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -30,6 +30,8 @@
     </declare-styleable>
     <declare-styleable name="RecentsPanelView">
         <attr name="recentItemLayout" format="reference" />
+        <!-- Style for the "Clear all" button. -->
+        <attr name="clearAllStyle" format="reference" />
     </declare-styleable>
     <declare-styleable name="DeadZone">
         <attr name="minSize" format="dimension" />
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 12f7881..b889343 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -81,8 +81,15 @@
     <!-- Height of a heads up notification in the status bar -->
     <dimen name="notification_max_heads_up_height">148dp</dimen>
 
-    <!-- Height of a the summary ("more card") notification on keyguard. -->
-    <dimen name="notification_summary_height">44dp</dimen>
+    <!-- Height of a the shelf with the notification icons -->
+    <dimen name="notification_shelf_height">32dp</dimen>
+
+    <!-- The padding of a notification icon on top to the start of the notification. Used for custom
+         views where the distance can't be measured -->
+    <dimen name="notification_icon_appear_padding">15dp</dimen>
+
+    <!-- The amount the content shifts upwards when transforming into the icon -->
+    <dimen name="notification_icon_transform_content_shift">32dp</dimen>
 
     <!-- Minimum layouted height of a notification in the statusbar-->
     <dimen name="min_notification_layout_height">48dp</dimen>
@@ -94,7 +101,7 @@
     <dimen name="notification_gear_padding">20dp</dimen>
 
     <!-- size at which Notification icons will be drawn in the status bar -->
-    <dimen name="status_bar_icon_drawing_size">17dip</dimen>
+    <dimen name="status_bar_icon_drawing_size">17dp</dimen>
 
     <!-- opacity at which Notification icons will be drawn in the status bar -->
     <item type="dimen" name="status_bar_icon_drawing_alpha">90%</item>
@@ -102,6 +109,15 @@
     <!-- gap on either side of status bar notification icons -->
     <dimen name="status_bar_icon_padding">0dp</dimen>
 
+    <!-- the padding on the start of the statusbar -->
+    <dimen name="status_bar_padding_start">6dp</dimen>
+
+    <!-- the radius of the overflow dot in the status bar -->
+    <dimen name="overflow_dot_radius">1dp</dimen>
+
+    <!-- the padding between dots in the icon overflow -->
+    <dimen name="overflow_icon_dot_padding">3dp</dimen>
+
     <!-- The padding on the global screenshot background image -->
     <dimen name="global_screenshot_bg_padding">20dp</dimen>
 
@@ -257,16 +273,10 @@
     <!-- Default distance from each snap target that GlowPadView considers a "hit" -->
     <dimen name="glowpadview_inner_radius">15dip</dimen>
 
-    <!-- Space reserved for the cards behind the top card in the bottom stack -->
-    <dimen name="bottom_stack_peek_amount">12dp</dimen>
-
     <!-- bottom_stack_peek_amount + notification_min_height
          + notification_collapse_second_card_padding -->
     <dimen name="min_stack_height">104dp</dimen>
 
-    <!-- The height of the area before the bottom stack in which the notifications slow down -->
-    <dimen name="bottom_stack_slow_down_length">12dp</dimen>
-
     <!-- Z distance between notifications if they are in the stack -->
     <dimen name="z_distance_between_notifications">0.5dp</dimen>
 
@@ -697,4 +707,19 @@
 
     <!-- The size of the PIP dismiss target. -->
     <dimen name="pip_dismiss_target_size">48dp</dimen>
+
+    <!-- Values specific to grid-based recents. -->
+    <!-- Margins around recent tasks. -->
+    <dimen name="recents_grid_margin_left">15dp</dimen>
+    <dimen name="recents_grid_margin_top">70dp</dimen>
+    <dimen name="recents_grid_margin_right">15dp</dimen>
+    <dimen name="recents_grid_margin_bottom">90dp</dimen>
+    <!-- Margins around the "Clear all" button. -->
+    <dimen name="recents_grid_clear_all_margin_left">0dp</dimen>
+    <dimen name="recents_grid_clear_all_margin_top">30dp</dimen>
+    <dimen name="recents_grid_clear_all_margin_right">15dp</dimen>
+    <dimen name="recents_grid_clear_all_margin_bottom">0dp</dimen>
+    <!-- Padding in between task views. -->
+    <dimen name="recents_grid_inter_task_padding">15dp</dimen>
+
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 94d79f2..8c80c71 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -16,18 +16,21 @@
   -->
 
 <resources>
+    <item type="id" name="translation_x_animator_tag"/>
     <item type="id" name="translation_y_animator_tag"/>
     <item type="id" name="translation_z_animator_tag"/>
     <item type="id" name="alpha_animator_tag"/>
     <item type="id" name="top_inset_animator_tag"/>
     <item type="id" name="height_animator_tag"/>
     <item type="id" name="shadow_alpha_animator_tag"/>
+    <item type="id" name="translation_x_animator_end_value_tag"/>
     <item type="id" name="translation_y_animator_end_value_tag"/>
     <item type="id" name="translation_z_animator_end_value_tag"/>
     <item type="id" name="alpha_animator_end_value_tag"/>
     <item type="id" name="top_inset_animator_end_value_tag"/>
     <item type="id" name="height_animator_end_value_tag"/>
     <item type="id" name="shadow_alpha_animator_end_value_tag"/>
+    <item type="id" name="translation_x_animator_start_value_tag"/>
     <item type="id" name="translation_y_animator_start_value_tag"/>
     <item type="id" name="translation_z_animator_start_value_tag"/>
     <item type="id" name="alpha_animator_start_value_tag"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 331d09e..5fec647 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1120,6 +1120,7 @@
         <item></item> <!-- STREAM_SYSTEM_ENFORCED -->
         <item></item> <!-- STREAM_DTMF -->
         <item></item> <!-- STREAM_TTS -->
+        <item>Accessibility</item> <!-- STREAM_ACCESSIBILITY -->
     </string-array>
 
     <string name="volume_stream_muted" translatable="false">%s silent</string>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6661f07..78fc9f9 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -34,6 +34,30 @@
         <item name="android:colorBackgroundCacheHint">@null</item>
         <item name="android:windowShowWallpaper">true</item>
         <item name="android:windowDisablePreview">true</item>
+        <item name="clearAllStyle">@style/ClearAllButtonDefaultMargins</item>
+    </style>
+
+    <style name="ClearAllButtonDefaultMargins">
+        <item name="android:layout_marginStart">0dp</item>
+        <item name="android:layout_marginTop">0dp</item>
+        <item name="android:layout_marginEnd">0dp</item>
+        <item name="android:layout_marginBottom">0dp</item>
+    </style>
+
+    <!-- Grid-based Recents theme. -->
+    <style name="RecentsTheme.Grid">
+        <item name="android:windowBackground">@color/transparent</item>
+        <item name="android:colorBackgroundCacheHint">@null</item>
+        <item name="android:windowShowWallpaper">true</item>
+        <item name="android:windowDisablePreview">true</item>
+        <item name="clearAllStyle">@style/ClearAllButtonLargeMargins</item>
+    </style>
+
+    <style name="ClearAllButtonLargeMargins">
+        <item name="android:layout_marginStart">@dimen/recents_grid_clear_all_margin_left</item>
+        <item name="android:layout_marginTop">@dimen/recents_grid_clear_all_margin_top</item>
+        <item name="android:layout_marginEnd">@dimen/recents_grid_clear_all_margin_right</item>
+        <item name="android:layout_marginBottom">@dimen/recents_grid_clear_all_margin_bottom</item>
     </style>
 
     <!-- Performance optimized Recents theme (no wallpaper) -->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9bbdfda..2192b8c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -20,6 +20,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
 
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -595,7 +596,7 @@
 
             if (any && !strongAuthTracker.hasUserAuthenticatedSinceBoot()) {
                 return KeyguardSecurityView.PROMPT_REASON_RESTART;
-            } else if (fingerprint && mUpdateMonitor.hasFingerprintUnlockTimedOut(currentUser)) {
+            } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_TIMEOUT) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_TIMEOUT;
             } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW) != 0) {
                 return KeyguardSecurityView.PROMPT_REASON_DEVICE_ADMIN;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java
index e1e654d5..71c2148 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java
@@ -20,16 +20,22 @@
 import android.app.Activity;
 import android.content.Intent;
 import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.util.DisplayMetrics;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.ScaleAnimation;
+import android.view.animation.TranslateAnimation;
 import android.widget.FrameLayout;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import com.android.internal.logging.MetricsLogger;
@@ -61,17 +67,23 @@
 import com.android.systemui.recents.views.TaskView;
 
 import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
 
 /**
  * The main grid recents activity started by the RecentsImpl.
  */
-public class RecentsGridActivity extends Activity implements ViewTreeObserver.OnPreDrawListener {
+public class RecentsGridActivity extends Activity {
+    public final static int MAX_VISIBLE_TASKS = 9;
+
     private final static String TAG = "RecentsGridActivity";
+    private final static int TITLE_BAR_HEIGHT_DP = 64;
+
+    private ArrayList<Integer> mMargins = new ArrayList<>();
 
     private TaskStack mTaskStack;
-    private List<Task> mTasks = new ArrayList<>();
-    private List<TaskView> mTaskViews = new ArrayList<>();
+    private ArrayList<Task> mTasks = new ArrayList<>();
+    private ArrayList<TaskView> mTaskViews = new ArrayList<>();
+    private ArrayList<Rect> mTaskViewRects;
     private FrameLayout mRecentsView;
     private TextView mEmptyView;
     private View mClearAllButton;
@@ -80,6 +92,10 @@
     private Rect mDisplayRect = new Rect();
     private LayoutInflater mInflater;
     private boolean mTouchExplorationEnabled;
+    private Point mScreenSize;
+    private int mTitleBarHeightPx;
+    private int mStatusBarHeightPx;
+    private int mNavigationBarHeightPx;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -87,12 +103,28 @@
         setContentView(R.layout.recents_grid);
         SystemServicesProxy ssp = Recents.getSystemServices();
 
+        Resources res = getResources();
+        Integer[] margins = {
+                res.getDimensionPixelSize(R.dimen.recents_grid_margin_left),
+                res.getDimensionPixelSize(R.dimen.recents_grid_margin_top),
+                res.getDimensionPixelSize(R.dimen.recents_grid_margin_right),
+                res.getDimensionPixelSize(R.dimen.recents_grid_margin_bottom),
+        };
+        mMargins.addAll(Arrays.asList(margins));
+
         mInflater = LayoutInflater.from(this);
         Configuration appConfiguration = Utilities.getAppConfiguration(this);
         mDisplayRect = ssp.getDisplayRect();
         mLastDisplayOrientation = appConfiguration.orientation;
         mLastDisplayDensity = appConfiguration.densityDpi;
         mTouchExplorationEnabled = ssp.isTouchExplorationEnabled();
+        mScreenSize = new Point();
+        getWindowManager().getDefaultDisplay().getRealSize(mScreenSize);
+        DisplayMetrics metrics = res.getDisplayMetrics();
+        mTitleBarHeightPx = (int) (TITLE_BAR_HEIGHT_DP *
+                ((float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT));
+        mStatusBarHeightPx = res.getDimensionPixelSize(R.dimen.status_bar_height);
+        mNavigationBarHeightPx = res.getDimensionPixelSize(R.dimen.navigation_bar_height);
 
         mRecentsView = (FrameLayout) findViewById(R.id.recents_view);
         mRecentsView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
@@ -100,8 +132,7 @@
                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
         getWindow().getAttributes().privateFlags |=
                 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
-        LinearLayout recentsContainer = (LinearLayout) findViewById(R.id.recents_container);
-        mEmptyView = (TextView) mInflater.inflate(R.layout.recents_empty, recentsContainer, false);
+        mEmptyView = (TextView) mInflater.inflate(R.layout.recents_empty, mRecentsView, false);
         mClearAllButton = findViewById(R.id.button);
 
         FrameLayout.LayoutParams emptyViewLayoutParams = new FrameLayout.LayoutParams(
@@ -111,8 +142,8 @@
         mRecentsView.addView(mEmptyView);
 
         mClearAllButton.setVisibility(View.VISIBLE);
-        LinearLayout.LayoutParams lp =
-                (LinearLayout.LayoutParams) mClearAllButton.getLayoutParams();
+        FrameLayout.LayoutParams lp =
+                (FrameLayout.LayoutParams) mClearAllButton.getLayoutParams();
         lp.gravity = Gravity.END;
 
         mClearAllButton.setOnClickListener(v -> {
@@ -154,6 +185,57 @@
         return null;
     }
 
+    /**
+     * Starts animations for each task view to either enlarge it to the size of the screen (when
+     * launching a task), or (if {@code reverse} is true, to reduce it from the size of the screen
+     * back to its place in the recents layout (when opening recents).
+     * @param animationListener An animation listener for executing code before or after the
+     *         animations run.
+     * @param reverse Whether the blow-up animations should be run in reverse.
+     */
+    private void startBlowUpAnimations(Animation.AnimationListener animationListener,
+            boolean reverse) {
+        if (mTaskViews.size() == 0) {
+            return;
+        }
+        int screenWidth = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE
+                ? mScreenSize.x : mScreenSize.y;
+        int screenHeight = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE
+                ? mScreenSize.y : mScreenSize.x;
+        screenHeight -= mStatusBarHeightPx + mNavigationBarHeightPx;
+        for (int i = 0; i < mTaskViews.size(); i++) {
+            View tv = mTaskViews.get(i);
+            AnimationSet animations = new AnimationSet(true /* shareInterpolator */);
+            animations.setInterpolator(new DecelerateInterpolator());
+            if (i == 0 && animationListener != null) {
+                animations.setAnimationListener(animationListener);
+            }
+            animations.setFillBefore(reverse);
+            animations.setFillAfter(!reverse);
+            Rect initialRect = mTaskViewRects.get(mTaskViewRects.size() - 1 - i);
+            int xDelta = - initialRect.left;
+            int yDelta = - initialRect.top - mTitleBarHeightPx + mStatusBarHeightPx;
+            TranslateAnimation translate = new TranslateAnimation(
+                    reverse ? xDelta : 0, reverse ? 0 : xDelta,
+                    reverse ? yDelta : 0, reverse ? 0 : yDelta);
+            translate.setDuration(250);
+            animations.addAnimation(translate);
+
+
+            float xScale = (float) screenWidth / (float) initialRect.width();
+            float yScale = (float) screenHeight /
+                    ((float) initialRect.height() - mTitleBarHeightPx);
+            ScaleAnimation scale = new ScaleAnimation(
+                    reverse ? xScale : 1, reverse ? 1 : xScale,
+                    reverse ? yScale : 1, reverse ? 1 : yScale,
+                    Animation.ABSOLUTE, 0, Animation.ABSOLUTE, mStatusBarHeightPx);
+            scale.setDuration(300);
+            animations.addAnimation(scale);
+
+            tv.startAnimation(animations);
+        }
+    }
+
     private void updateControlVisibility() {
         boolean empty = (mTasks.size() == 0);
         mClearAllButton.setVisibility(empty ? View.INVISIBLE : View.VISIBLE);
@@ -163,7 +245,7 @@
         }
     }
 
-    private void updateRecentsTasks() {
+    private void updateModel() {
         RecentsTaskLoader loader = Recents.getTaskLoader();
         RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan();
         if (plan == null) {
@@ -174,29 +256,66 @@
         if (!plan.hasTasks()) {
             loader.preloadTasks(plan, -1, !launchState.launchedFromHome);
         }
-        int numVisibleTasks = 9;
         mTaskStack = plan.getTaskStack();
         RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
         loadOpts.runningTaskId = launchState.launchedToTaskId;
-        loadOpts.numVisibleTasks = numVisibleTasks;
-        loadOpts.numVisibleTaskThumbnails = numVisibleTasks;
+        loadOpts.numVisibleTasks = MAX_VISIBLE_TASKS;
+        loadOpts.numVisibleTaskThumbnails = MAX_VISIBLE_TASKS;
         loader.loadTasks(this, plan, loadOpts);
 
         mTasks = mTaskStack.getStackTasks();
+    }
 
-        updateControlVisibility();
-
-        clearTaskViews();
+    private void updateViews() {
+        int screenWidth = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE
+                ? mScreenSize.x : mScreenSize.y;
+        int screenHeight = mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE
+                ? mScreenSize.y : mScreenSize.x;
+        int paddingPixels = getResources().getDimensionPixelSize(
+                R.dimen.recents_grid_inter_task_padding);
+        mTaskViewRects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
+                mTasks.size(), screenWidth, screenHeight, getAppRectRatio(), paddingPixels,
+                mMargins, mTitleBarHeightPx);
+        boolean recycleViews = (mTaskViews.size() == mTasks.size());
+        if (!recycleViews) {
+            clearTaskViews();
+        }
         for (int i = 0; i < mTasks.size(); i++) {
             Task task = mTasks.get(i);
-            TaskView taskView = createView();
+            // We keep the same ordering in the model as other Recents flavors (older tasks are
+            // first in the stack) so that the logic can be similar, but we reverse the order
+            // when placing views on the screen so that most recent tasks are displayed first.
+            Rect rect = mTaskViewRects.get(mTaskViewRects.size() - 1 - i);
+            TaskView taskView;
+            if (recycleViews) {
+                taskView = mTaskViews.get(i);
+            } else {
+                taskView = createView();
+            }
             taskView.onTaskBound(task, mTouchExplorationEnabled, mLastDisplayOrientation,
                     mDisplayRect);
             Recents.getTaskLoader().loadTaskData(task);
             taskView.setTouchEnabled(true);
             // Show dismiss button right away.
             taskView.startNoUserInteractionAnimation();
-            mTaskViews.add(taskView);
+            taskView.setLayoutParams(new FrameLayout.LayoutParams(rect.width(), rect.height()));
+            taskView.setTranslationX(rect.left);
+            taskView.setTranslationY(rect.top);
+            if (!recycleViews) {
+                mRecentsView.addView(taskView);
+                mTaskViews.add(taskView);
+            }
+        }
+        updateControlVisibility();
+    }
+
+    private float getAppRectRatio() {
+        if (mLastDisplayOrientation == Configuration.ORIENTATION_LANDSCAPE) {
+            return (float) mScreenSize.x /
+                    (float) (mScreenSize.y - mStatusBarHeightPx - mNavigationBarHeightPx);
+        } else {
+            return (float) mScreenSize.y /
+                    (float) (mScreenSize.x - mStatusBarHeightPx - mNavigationBarHeightPx);
         }
     }
 
@@ -204,13 +323,23 @@
     protected void onStart() {
         super.onStart();
         EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true));
-        mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
-    }
+        updateModel();
+        updateViews();
+        if (mTaskViews.size() > 0) {
+            mTaskViews.get(mTaskViews.size() - 1).bringToFront();
+        }
+        startBlowUpAnimations(new Animation.AnimationListener() {
+            @Override
+            public void onAnimationStart(Animation animation) { }
 
-    @Override
-    public void onResume() {
-        super.onResume();
-        updateRecentsTasks();
+            @Override
+            public void onAnimationEnd(Animation animation) {
+                updateViews();
+            }
+
+            @Override
+            public void onAnimationRepeat(Animation animation) { }
+        }, true /* reverse */);
     }
 
     @Override
@@ -237,37 +366,13 @@
         // Notify of the config change.
         Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this);
         mDisplayRect = Recents.getSystemServices().getDisplayRect();
-        mRecentsView.getViewTreeObserver().addOnPreDrawListener(this);
-        mRecentsView.requestLayout();
         int numStackTasks = mTaskStack.getStackTaskCount();
         EventBus.getDefault().send(new ConfigurationChangedEvent(false /* fromMultiWindow */,
                 mLastDisplayOrientation != newDeviceConfiguration.orientation,
                 mLastDisplayDensity != newDeviceConfiguration.densityDpi, numStackTasks > 0));
         mLastDisplayOrientation = newDeviceConfiguration.orientation;
         mLastDisplayDensity = newDeviceConfiguration.densityDpi;
-    }
-
-    @Override
-    public boolean onPreDraw() {
-        mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
-        int width = mRecentsView.getWidth();
-        int height = mRecentsView.getHeight();
-
-        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
-            mTasks.size(), width, height, false /* allowLineOfThree */, 30 /* padding */);
-        removeTaskViews();
-        for (int i = 0; i < rects.size(); i++) {
-            Rect rect = rects.get(i);
-            // We keep the same ordering in the model as other Recents flavors (older tasks are
-            // first in the stack) so that the logic can be similar, but we reverse the order
-            // when placing views on the screen so that most recent tasks are displayed first.
-            View taskView = mTaskViews.get(rects.size() - 1 - i);
-            taskView.setLayoutParams(new FrameLayout.LayoutParams(rect.width(), rect.height()));
-            taskView.setTranslationX(rect.left);
-            taskView.setTranslationY(rect.top);
-            mRecentsView.addView(taskView);
-        }
-        return true;
+        updateViews();
     }
 
     void dismissRecentsToHome() {
@@ -360,7 +465,8 @@
             EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i)));
         }
         mTasks = new ArrayList<>();
-        updateRecentsTasks();
+        updateModel();
+        updateViews();
 
         MetricsLogger.action(this, MetricsEvent.OVERVIEW_DISMISS_ALL);
     }
@@ -390,6 +496,9 @@
     }
 
     public final void onBusEvent(LaunchTaskEvent event) {
+        event.taskView.bringToFront();
         startActivity(event.task.key.baseIntent);
+        // Eventually we should start blow-up animations here, but we need to make sure it's done
+        // in parallel with starting the activity so that we don't introduce unneeded latency.
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java
index 057aa54..648f2f0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java
@@ -19,66 +19,111 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 class TaskGridLayoutAlgorithm {
 
-    private static final String TAG = "TaskGridLayoutAlgorithm";
-
-    static List<Rect> getRectsForTaskCount(int count, int containerWidth, int containerHeight,
-            boolean allowLineOfThree, int padding) {
-        return getRectsForTaskCount(count, containerWidth, containerHeight, allowLineOfThree,
-                padding, null);
+    public enum VerticalGravity {
+        START, END, CENTER
     }
 
-    static List<Rect> getRectsForTaskCount(int count, int containerWidth, int containerHeight,
-            boolean allowLineOfThree, int padding, Rect preCalculatedTile) {
-        int singleLineMaxCount = allowLineOfThree ? 3 : 2;
-        List<Rect> rects = new ArrayList<>(count);
+    public static final List<Integer> ZERO_MARGIN = new ArrayList<>();
+    static {
+        Integer[] zero = {0, 0, 0, 0};
+        ZERO_MARGIN.addAll(Arrays.asList(zero));
+    }
+    private static final String TAG = "TaskGridLayoutAlgorithm";
+
+    /**
+     * Calculates the adequate rectangles for the specified number of tasks to be layed out on
+     * the screen.
+     * @param count The number of task views to layout.
+     * @param containerWidth The width of the whole area containing those tasks.
+     * @param containerHeight The height of the whole area containing those tasks.
+     * @param screenRatio The ratio of the device's screen, so that tasks have the same aspect
+     *         ratio (ignoring the title bar).
+     * @param padding The amount of padding, in pixels, in between task views.
+     * @param margins The amount of space to be left blank around the area on the left, top, right
+     *         and bottom.
+     * @param titleBarHeight The height, in pixels, of the task views title bar.
+     * @return A list of rectangles to be used for layout.
+     */
+    static ArrayList<Rect> getRectsForTaskCount(int count, int containerWidth, int containerHeight,
+            float screenRatio, int padding, List<Integer> margins, int titleBarHeight) {
+        return getRectsForTaskCount(count, containerWidth, containerHeight,  screenRatio, padding,
+                margins, titleBarHeight, null, VerticalGravity.CENTER);
+    }
+
+    private static ArrayList<Rect> getRectsForTaskCount(int count, int containerWidth,
+            int containerHeight, float screenRatio, int padding, List<Integer> margins,
+            int titleBarHeight, Rect preCalculatedTile, VerticalGravity gravity) {
+        ArrayList<Rect> rects = new ArrayList<>(count);
         boolean landscape = (containerWidth > containerHeight);
+        containerWidth -= margins.get(0) + margins.get(2);
+        containerHeight -= margins.get(1) + margins.get(3);
 
         // We support at most 9 tasks in this layout.
-        count = Math.min(count, 9);
+        count = Math.min(count, RecentsGridActivity.MAX_VISIBLE_TASKS);
 
         if (count == 0) {
             return rects;
         }
-        if (count <= singleLineMaxCount) {
-            if (landscape) {
-                // Single line.
-                int taskWidth = 0;
-                int emptySpace = 0;
-                if (preCalculatedTile != null) {
-                    taskWidth = preCalculatedTile.width();
-                    emptySpace = containerWidth - (count * taskWidth) - (count - 1) * padding;
-                } else {
-                    // Divide available space in equal parts.
-                    taskWidth = (containerWidth - (count - 1) * padding) / count;
-                }
-                for (int i = 0; i < count; i++) {
-                    int left = emptySpace / 2 + i * taskWidth + i * padding;
-                    rects.add(new Rect(left, 0, left + taskWidth, containerHeight));
-                }
+        if (count <= 3) {
+            // Base case: single line.
+            int taskWidth, taskHeight;
+            if (preCalculatedTile != null) {
+                taskWidth = preCalculatedTile.width();
+                taskHeight = preCalculatedTile.height();
             } else {
-                // Single column. Divide available space in equal parts.
-                int taskHeight = (containerHeight - (count - 1) * padding) / count;
-                for (int i = 0; i < count; i++) {
-                    int top = i * taskHeight + i * padding;
-                    rects.add(new Rect(0, top, containerWidth, top + taskHeight));
+                // Divide available width in equal parts.
+                int maxTaskWidth = (containerWidth - (count - 1) * padding) / count;
+                int maxTaskHeight = containerHeight;
+                if (maxTaskHeight >= maxTaskWidth / screenRatio + titleBarHeight) {
+                    // Width bound.
+                    taskWidth = maxTaskWidth;
+                    taskHeight = (int) (maxTaskWidth / screenRatio + titleBarHeight);
+                } else {
+                    // Height bound.
+                    taskHeight = maxTaskHeight;
+                    taskWidth = (int) ((taskHeight - titleBarHeight) * screenRatio);
                 }
             }
+            int emptySpaceX = containerWidth - (count * taskWidth) - (count - 1) * padding;
+            int emptySpaceY = containerHeight - taskHeight;
+            for (int i = 0; i < count; i++) {
+                int left = emptySpaceX / 2 + i * taskWidth + i * padding;
+                int top;
+                switch (gravity) {
+                    case CENTER:
+                        top = emptySpaceY / 2;
+                        break;
+                    case END:
+                        top = emptySpaceY;
+                        break;
+                    case START:
+                    default:
+                        top = 0;
+                        break;
+                }
+                Rect rect = new Rect(left, top, left + taskWidth, top + taskHeight);
+                rect.offset(margins.get(0), margins.get(1));
+                rects.add(rect);
+            }
         } else if (count < 7) {
             // Two lines.
             int lineHeight = (containerHeight - padding) / 2;
             int lineTaskCount = (int) Math.ceil((double) count / 2);
-            List<Rect> rectsA = getRectsForTaskCount(
-                    lineTaskCount, containerWidth, lineHeight, true /* allowLineOfThree */, padding,
-                            null);
-            List<Rect> rectsB = getRectsForTaskCount(
-                    count - lineTaskCount, containerWidth, lineHeight, true /* allowLineOfThree */,
-                            padding, rectsA.get(0));
-            for (Rect rect : rectsB) {
-                rect.offset(0, lineHeight + padding);
+            List<Rect> rectsA = getRectsForTaskCount(lineTaskCount, containerWidth, lineHeight,
+                    screenRatio, padding, ZERO_MARGIN, titleBarHeight, null, VerticalGravity.END);
+            List<Rect> rectsB = getRectsForTaskCount(count - lineTaskCount, containerWidth,
+                    lineHeight, screenRatio, padding, ZERO_MARGIN, titleBarHeight, rectsA.get(0),
+                    VerticalGravity.START);
+            for (int i = 0; i < rectsA.size(); i++) {
+                rectsA.get(i).offset(margins.get(0), margins.get(1));
+            }
+            for (int i = 0; i < rectsB.size(); i++) {
+                rectsB.get(i).offset(margins.get(0), margins.get(1) + lineHeight + padding);
             }
             rects.addAll(rectsA);
             rects.addAll(rectsB);
@@ -86,19 +131,22 @@
             // Three lines.
             int lineHeight = (containerHeight - 2 * padding) / 3;
             int lineTaskCount = (int) Math.ceil((double) count / 3);
-            List<Rect> rectsA = getRectsForTaskCount(
-                lineTaskCount, containerWidth, lineHeight, true /* allowLineOfThree */, padding, null);
-            List<Rect> rectsB = getRectsForTaskCount(
-                lineTaskCount, containerWidth, lineHeight, true /* allowLineOfThree */, padding,
-                        rectsA.get(0));
-            List<Rect> rectsC = getRectsForTaskCount(
-                count - (2 * lineTaskCount), containerWidth, lineHeight,
-                         true /* allowLineOfThree */, padding, rectsA.get(0));
-            for (Rect rect : rectsB) {
-                rect.offset(0, lineHeight + padding);
+            List<Rect> rectsA = getRectsForTaskCount(lineTaskCount, containerWidth, lineHeight,
+                    screenRatio, padding, ZERO_MARGIN, titleBarHeight, null, VerticalGravity.END);
+            List<Rect> rectsB = getRectsForTaskCount(lineTaskCount, containerWidth, lineHeight,
+                    screenRatio,  padding, ZERO_MARGIN, titleBarHeight, rectsA.get(0),
+                    VerticalGravity.END);
+            List<Rect> rectsC = getRectsForTaskCount(count - (2 * lineTaskCount), containerWidth,
+                    lineHeight, screenRatio, padding, ZERO_MARGIN, titleBarHeight, rectsA.get(0),
+                    VerticalGravity.START);
+            for (int i = 0; i < rectsA.size(); i++) {
+                rectsA.get(i).offset(margins.get(0), margins.get(1));
             }
-            for (Rect rect : rectsC) {
-                rect.offset(0, 2 * (lineHeight + padding));
+            for (int i = 0; i < rectsB.size(); i++) {
+                rectsB.get(i).offset(margins.get(0), margins.get(1) + lineHeight + padding);
+            }
+            for (int i = 0; i < rectsC.size(); i++) {
+                rectsC.get(i).offset(margins.get(0), margins.get(1) + 2 * (lineHeight + padding));
             }
             rects.addAll(rectsA);
             rects.addAll(rectsB);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index bc46548..173f160 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -41,7 +41,7 @@
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
- * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
+ * Base class for both {@link ExpandableNotificationRow} and {@link NotificationShelf}
  * to implement dimming/activating on Keyguard for the double-tap gesture
  */
 public abstract class ActivatableNotificationView extends ExpandableOutlineView {
@@ -86,6 +86,12 @@
      */
     private static final float DARK_EXIT_SCALE_START = 0.93f;
 
+    /**
+     * A sentinel value when no color should be used. Can be used with {@link #setTintColor(int)}
+     * or {@link #setOverrideTintColor(int, float)}.
+     */
+    protected static final int NO_COLOR = 0;
+
     private static final Interpolator ACTIVATE_INVERSE_INTERPOLATOR
             = new PathInterpolator(0.6f, 0, 0.5f, 1);
     private static final Interpolator ACTIVATE_INVERSE_ALPHA_INTERPOLATOR
@@ -167,6 +173,8 @@
     private int mCurrentBackgroundTint;
     private int mTargetTint;
     private int mStartTint;
+    private int mOverrideTint;
+    private float mOverrideAmount;
 
     public ActivatableNotificationView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -448,9 +456,20 @@
         if (below != mIsBelowSpeedBump) {
             mIsBelowSpeedBump = below;
             updateBackgroundTint();
+            onBelowSpeedBumpChanged();
         }
     }
 
+    protected void onBelowSpeedBumpChanged() {
+    }
+
+    /**
+     * @return whether we are below the speed bump
+     */
+    public boolean isBelowSpeedBump() {
+        return mIsBelowSpeedBump;
+    }
+
     /**
      * Sets the tint color of the background
      */
@@ -466,6 +485,23 @@
         updateBackgroundTint(animated);
     }
 
+    /**
+     * Set an override tint color that is used for the background.
+     *
+     * @param color the color that should be used to tint the background.
+     *              This can be {@link #NO_COLOR} if the tint should be normally computed.
+     * @param overrideAmount a value from 0 to 1 how much the override tint should be used. The
+     *                       background color will then be the interpolation between this and the
+     *                       regular background color, where 1 means the overrideTintColor is fully
+     *                       used and the background color not at all.
+     */
+    public void setOverrideTintColor(int color, float overrideAmount) {
+        mOverrideTint = color;
+        mOverrideAmount = overrideAmount;
+        int newColor = calculateBgColor();
+        setBackgroundTintColor(newColor);
+    }
+
     protected void updateBackgroundTint() {
         updateBackgroundTint(false /* animated */);
     }
@@ -673,6 +709,13 @@
     }
 
     @Override
+    public void setClipBottomAmount(int clipBottomAmount) {
+        super.setClipBottomAmount(clipBottomAmount);
+        mBackgroundNormal.setClipBottomAmount(clipBottomAmount);
+        mBackgroundDimmed.setClipBottomAmount(clipBottomAmount);
+    }
+
+    @Override
     public void performRemoveAnimation(long duration, float translationDirection,
             Runnable onFinishedRunnable) {
         enableAppearDrawing(true);
@@ -841,11 +884,20 @@
     protected abstract View getContentView();
 
     public int calculateBgColor() {
-        return calculateBgColor(true /* withTint */);
+        return calculateBgColor(true /* withTint */, true /* withOverRide */);
     }
 
-    private int calculateBgColor(boolean withTint) {
-        if (withTint && mBgTint != 0) {
+    /**
+     * @param withTint should a possible tint be factored in?
+     * @param withOverRide should the value be interpolated with {@link #mOverrideTint}
+     * @return the calculated background color
+     */
+    private int calculateBgColor(boolean withTint, boolean withOverRide) {
+        if (withOverRide && mOverrideTint != NO_COLOR) {
+            int defaultTint = calculateBgColor(withTint, false);
+            return NotificationUtils.interpolateColors(defaultTint, mOverrideTint, mOverrideAmount);
+        }
+        if (withTint && mBgTint != NO_COLOR) {
             return mBgTint;
         } else if (mShowingLegacyBackground) {
             return mLegacyColor;
@@ -936,7 +988,7 @@
     }
 
     public int getBackgroundColorWithoutTint() {
-        return calculateBgColor(false /* withTint */);
+        return calculateBgColor(false /* withTint */, false /* withOverride */);
     }
 
     public interface OnActivatedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 19e511cf..f5ca678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -42,7 +42,6 @@
 import android.database.ContentObserver;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
@@ -258,7 +257,7 @@
     protected boolean mShowLockscreenNotifications;
     protected boolean mAllowLockscreenRemoteInput;
 
-    protected NotificationOverflowContainer mKeyguardIconOverflowContainer;
+    protected NotificationShelf mNotificationShelf;
     protected DismissView mDismissView;
     protected EmptyShadeView mEmptyShadeView;
 
@@ -1025,9 +1024,7 @@
             }
         }
 
-        if (entry.icon != null) {
-            entry.icon.setTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
-        }
+        entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
     }
 
     public boolean isMediaNotification(NotificationData.Entry entry) {
@@ -1485,7 +1482,7 @@
      */
     @Override  // NotificationData.Environment
     public boolean shouldHideNotifications(int userId) {
-        return isLockscreenPublicMode(mCurrentUserId) && !userAllowsNotificationsInPublic(userId)
+        return isLockscreenPublicMode(userId) && !userAllowsNotificationsInPublic(userId)
                 || (userId != mCurrentUserId && shouldHideNotifications(mCurrentUserId));
     }
 
@@ -2160,13 +2157,14 @@
         if (DEBUG) {
             Log.d(TAG, "createNotificationViews(notification=" + sbn);
         }
-        final StatusBarIconView iconView = createIcon(sbn);
-        if (iconView == null) {
-            return null;
+        NotificationData.Entry entry = new NotificationData.Entry(sbn);
+        try {
+            entry.createIcons(mContext, sbn);
+        } catch (NotificationData.IconException exception) {
+            handleNotificationError(sbn, exception.getMessage());
         }
 
         // Construct the expanded view.
-        NotificationData.Entry entry = new NotificationData.Entry(sbn, iconView);
         if (!inflateViews(entry, mStackScroller)) {
             handleNotificationError(sbn, "Couldn't expand RemoteViews for: " + sbn);
             return null;
@@ -2174,33 +2172,6 @@
         return entry;
     }
 
-    public StatusBarIconView createIcon(StatusBarNotification sbn) {
-        // Construct the icon.
-        Notification n = sbn.getNotification();
-        final StatusBarIconView iconView = new StatusBarIconView(mContext,
-                sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
-        iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-
-        final Icon smallIcon = n.getSmallIcon();
-        if (smallIcon == null) {
-            handleNotificationError(sbn,
-                    "No small icon in notification from " + sbn.getPackageName());
-            return null;
-        }
-        final StatusBarIcon ic = new StatusBarIcon(
-                sbn.getUser(),
-                sbn.getPackageName(),
-                smallIcon,
-                n.iconLevel,
-                n.number,
-                StatusBarIconView.contentDescForNotification(mContext, n));
-        if (!iconView.set(ic)) {
-            handleNotificationError(sbn, "Couldn't create icon: " + ic);
-            return null;
-        }
-        return iconView;
-    }
-
     protected void addNotificationViews(Entry entry, RankingMap ranking) {
         if (entry == null) {
             return;
@@ -2220,17 +2191,16 @@
      * Updates expanded, dimmed and locked states of notification rows.
      */
     protected void updateRowStates() {
-        mKeyguardIconOverflowContainer.getIconsView().removeAllViews();
-
         ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
         final int N = activeNotifications.size();
 
         int visibleNotifications = 0;
         boolean onKeyguard = mState == StatusBarState.KEYGUARD;
-        int maxNotifications = 0;
+        int maxNotifications = -1;
         if (onKeyguard) {
             maxNotifications = getMaxKeyguardNotifications(true /* recompute */);
         }
+        mStackScroller.setMaxDisplayedNotifications(maxNotifications);
         for (int i = 0; i < N; i++) {
             NotificationData.Entry entry = activeNotifications.get(i);
             boolean childNotification = mGroupManager.isChildInGroupWithSummary(entry.notification);
@@ -2249,12 +2219,8 @@
             boolean showOnKeyguard = shouldShowOnKeyguard(entry.notification);
             if (suppressedSummary
                     || (isLockscreenPublicMode(userId) && !mShowLockscreenNotifications)
-                    || (onKeyguard && !childWithVisibleSummary
-                            && (visibleNotifications >= maxNotifications || !showOnKeyguard))) {
+                    || (onKeyguard && !showOnKeyguard)) {
                 entry.row.setVisibility(View.GONE);
-                if (onKeyguard && showOnKeyguard && !childNotification && !suppressedSummary) {
-                    mKeyguardIconOverflowContainer.getIconsView().addNotification(entry);
-                }
             } else {
                 boolean wasGone = entry.row.getVisibility() == View.GONE;
                 entry.row.setVisibility(View.VISIBLE);
@@ -2269,13 +2235,9 @@
             }
         }
 
-        mStackScroller.updateOverflowContainerVisibility(onKeyguard
-                && mKeyguardIconOverflowContainer.getIconsView().getChildCount() > 0);
-
         mStackScroller.changeViewPosition(mDismissView, mStackScroller.getChildCount() - 1);
         mStackScroller.changeViewPosition(mEmptyShadeView, mStackScroller.getChildCount() - 2);
-        mStackScroller.changeViewPosition(mKeyguardIconOverflowContainer,
-                mStackScroller.getChildCount() - 3);
+        mStackScroller.changeViewPosition(mNotificationShelf, mStackScroller.getChildCount() - 3);
     }
 
     public boolean shouldShowOnKeyguard(StatusBarNotification sbn) {
@@ -2367,48 +2329,28 @@
         mGroupManager.onEntryUpdated(entry, oldNotification);
 
         boolean updateSuccessful = false;
-        if (applyInPlace) {
-            if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
-            try {
-                if (entry.icon != null) {
-                    // Update the icon
-                    final StatusBarIcon ic = new StatusBarIcon(
-                            notification.getUser(),
-                            notification.getPackageName(),
-                            n.getSmallIcon(),
-                            n.iconLevel,
-                            n.number,
-                            StatusBarIconView.contentDescForNotification(mContext, n));
-                    entry.icon.setNotification(n);
-                    if (!entry.icon.set(ic)) {
-                        handleNotificationError(notification, "Couldn't update icon: " + ic);
-                        return;
-                    }
+        try {
+            if (applyInPlace) {
+                if (DEBUG) Log.d(TAG, "reusing notification for key: " + key);
+                try {
+                    entry.updateIcons(mContext, n);
+                    updateNotificationViews(entry, notification);
+                    updateSuccessful = true;
+                } catch (RuntimeException e) {
+                    // It failed to apply cleanly.
+                    Log.w(TAG, "Couldn't reapply views for package " +
+                            notification.getPackageName(), e);
                 }
-                updateNotificationViews(entry, notification);
-                updateSuccessful = true;
             }
-            catch (RuntimeException e) {
-                // It failed to apply cleanly.
-                Log.w(TAG, "Couldn't reapply views for package " +
-                        notification.getPackageName(), e);
+            if (!updateSuccessful) {
+                entry.updateIcons(mContext, n);
+                if (!inflateViews(entry, mStackScroller)) {
+                    handleNotificationError(notification, "Couldn't update remote views for: "
+                            + notification);
+                }
             }
-        }
-        if (!updateSuccessful) {
-            if (DEBUG) Log.d(TAG, "not reusing notification for key: " + key);
-            final StatusBarIcon ic = new StatusBarIcon(
-                    notification.getUser(),
-                    notification.getPackageName(),
-                    n.getSmallIcon(),
-                    n.iconLevel,
-                    n.number,
-                    StatusBarIconView.contentDescForNotification(mContext, n));
-            entry.icon.setNotification(n);
-            entry.icon.set(ic);
-            if (!inflateViews(entry, mStackScroller)) {
-                handleNotificationError(notification, "Couldn't update remote views for: "
-                        + notification);
-            }
+        } catch (NotificationData.IconException e) {
+            handleNotificationError(notification, e.getMessage());
         }
         updateHeadsUp(key, entry, shouldPeek, alertAgain);
         updateNotifications();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
index 1d7bede..5436664 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/DismissView.java
@@ -22,12 +22,17 @@
 import android.view.View;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.stack.ExpandableViewState;
+import com.android.systemui.statusbar.stack.StackScrollState;
 
 public class DismissView extends StackScrollerDecorView {
+    private final int mClearAllTopPadding;
     private DismissViewButton mDismissButton;
 
     public DismissView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mClearAllTopPadding = context.getResources().getDimensionPixelSize(
+                R.dimen.clear_all_padding_top);
     }
 
     @Override
@@ -63,4 +68,21 @@
     public boolean isButtonVisible() {
         return mDismissButton.getAlpha() != 0.0f;
     }
+
+    @Override
+    public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
+        return new DismissViewState();
+    }
+
+    public class DismissViewState extends ExpandableViewState {
+        @Override
+        public void applyToView(View view) {
+            super.applyToView(view);
+            if (view instanceof DismissView) {
+                DismissView dismissView = (DismissView) view;
+                boolean visible = this.clipTopAmount < mClearAllTopPadding;
+                dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone());
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
index 5db0699..19b32af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/EmptyShadeView.java
@@ -23,6 +23,8 @@
 import android.widget.TextView;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.stack.ExpandableViewState;
+import com.android.systemui.statusbar.stack.StackScrollState;
 
 public class EmptyShadeView extends StackScrollerDecorView {
 
@@ -40,4 +42,22 @@
     protected View findContentView() {
         return findViewById(R.id.no_notifications);
     }
+
+    @Override
+    public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
+        return new EmptyShadeViewState();
+    }
+
+    public static class EmptyShadeViewState extends ExpandableViewState {
+        @Override
+        public void applyToView(View view) {
+            super.applyToView(view);
+            if (view instanceof EmptyShadeView) {
+                EmptyShadeView emptyShadeView = (EmptyShadeView) view;
+                boolean visible = this.clipTopAmount <= 0;
+                emptyShadeView.performVisibilityAnimation(
+                        visible && !emptyShadeView.willBeGone());
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 5173176..a1384dcb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -36,6 +36,7 @@
 import android.view.MotionEvent;
 import android.view.NotificationHeaderView;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
@@ -45,16 +46,18 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.util.NotificationColorUtil;
+import com.android.internal.widget.CachingIconView;
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingManager;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.statusbar.stack.ExpandableViewState;
 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.stack.StackScrollState;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
-import com.android.systemui.statusbar.stack.StackViewState;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -63,6 +66,8 @@
 
     private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
     private static final int COLORED_DIVIDER_ALPHA = 0x7B;
+    private int mIconTransformContentShift;
+    private int mIconTransformContentShiftNoIcon;
     private int mNotificationMinHeightLegacy;
     private int mMaxHeadsUpHeightLegacy;
     private int mMaxHeadsUpHeight;
@@ -188,6 +193,10 @@
     private View mChildAfterViewWhenDismissed;
     private View mGroupParentWhenDismissed;
     private boolean mRefocusOnDismiss;
+    private float mIconTransformationAmount;
+    private boolean mIconsVisible = true;
+    private boolean mAboveShelf;
+    private boolean mIsLastChild;
 
     public boolean isGroupExpansionChanging() {
         if (isChildInGroup()) {
@@ -293,6 +302,7 @@
         // The public layouts expand button is always visible
         mPublicLayout.updateExpandButtons(true);
         updateLimits();
+        updateIconVisibilities();
     }
 
     private void updateLimits() {
@@ -318,6 +328,10 @@
         return mStatusBarNotification;
     }
 
+    public NotificationData.Entry getEntry() {
+        return mEntry;
+    }
+
     public boolean isHeadsUp() {
         return mIsHeadsUp;
     }
@@ -333,6 +347,9 @@
         if (intrinsicBefore != getIntrinsicHeight()) {
             notifyHeightChanged(false  /* needsAnimation */);
         }
+        if (isHeadsUp) {
+            setAboveShelf(true);
+        }
     }
 
     public void setGroupManager(NotificationGroupManager groupManager) {
@@ -400,6 +417,7 @@
         if (mNotificationParent != null) {
             mNotificationParent.updateBackgroundForGroupState();
         }
+        updateIconVisibilities();
     }
 
     @Override
@@ -459,7 +477,7 @@
 
     public void getChildrenStates(StackScrollState resultState) {
         if (mIsSummaryWithChildren) {
-            StackViewState parentState = resultState.getViewStateForView(this);
+            ExpandableViewState parentState = resultState.getViewStateForView(this);
             mChildrenContainer.getState(resultState, parentState);
         }
     }
@@ -476,11 +494,9 @@
         }
     }
 
-    public void startChildAnimation(StackScrollState finalState,
-            StackStateAnimator stateAnimator, long delay, long duration) {
+    public void startChildAnimation(StackScrollState finalState, AnimationProperties properties) {
         if (mIsSummaryWithChildren) {
-            mChildrenContainer.startAnimationToState(finalState, stateAnimator, delay,
-                    duration);
+            mChildrenContainer.startAnimationToState(finalState, properties);
         }
     }
 
@@ -522,12 +538,17 @@
         return mIsPinned;
     }
 
+    @Override
+    public int getPinnedHeadsUpHeight() {
+        return getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
+    }
+
     /**
      * @param atLeastMinHeight should the value returned be at least the minimum height.
      *                         Used to avoid cyclic calls
      * @return the height of the heads up notification when pinned
      */
-    public int getPinnedHeadsUpHeight(boolean atLeastMinHeight) {
+    private int getPinnedHeadsUpHeight(boolean atLeastMinHeight) {
         if (mIsSummaryWithChildren) {
             return mChildrenContainer.getIntrinsicHeight();
         }
@@ -764,9 +785,17 @@
         return mChildrenContainer;
     }
 
-    public void setHeadsupDisappearRunning(boolean running) {
-        mHeadsupDisappearRunning = running;
-        mPrivateLayout.setHeadsupDisappearRunning(running);
+    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        mHeadsupDisappearRunning = headsUpAnimatingAway;
+        mPrivateLayout.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+    }
+
+    /**
+     * @return if the view was just heads upped and is now animating away. During such a time the
+     * layout needs to be kept consistent
+     */
+    public boolean isHeadsUpAnimatingAway() {
+        return mHeadsupDisappearRunning;
     }
 
     public View getChildAfterViewWhenDismissed() {
@@ -785,6 +814,105 @@
         mVetoButton.setOnClickListener(listener);
     }
 
+    public View getNotificationIcon() {
+        NotificationHeaderView notificationHeader = getNotificationHeader();
+        if (notificationHeader != null) {
+            return notificationHeader.getIcon();
+        }
+        return null;
+    }
+
+    /**
+     * @return whether the notification is currently showing a view with an icon.
+     */
+    public boolean isShowingIcon() {
+        if (mIsSummaryWithChildren) {
+            return true;
+        }
+        NotificationContentView showingLayout = getShowingLayout();
+        NotificationHeaderView notificationHeader = showingLayout.getVisibleNotificationHeader();
+        return notificationHeader != null;
+    }
+
+    /**
+     * Set how much this notification is transformed into an icon.
+     *
+     * @param iconTransformationAmount A value from 0 to 1 indicating how much we are transformed
+     *                                 to an icon
+     * @param isLastChild is this the last child in the list. If true, then the transformation is
+     *                    different since it's content fades out.
+     */
+    public void setIconTransformationAmount(float iconTransformationAmount, boolean isLastChild) {
+        boolean changeTransformation = isLastChild != mIsLastChild;
+        changeTransformation |= mIconTransformationAmount != iconTransformationAmount;
+        mIsLastChild = isLastChild;
+        mIconTransformationAmount = iconTransformationAmount;
+        if (changeTransformation) {
+            updateContentTransformation();
+            boolean iconsVisible = mIconTransformationAmount == 0.0f;
+            if (iconsVisible != mIconsVisible) {
+                mIconsVisible = iconsVisible;
+                updateIconVisibilities();
+            }
+        }
+    }
+
+    @Override
+    protected void onBelowSpeedBumpChanged() {
+        updateIconVisibilities();
+    }
+
+    private void updateContentTransformation() {
+        float contentAlpha;
+        float translationY = - mIconTransformationAmount * mIconTransformContentShift;
+        if (mIsLastChild) {
+            contentAlpha = 1.0f - mIconTransformationAmount;
+            contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f);
+            contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha);
+            translationY *= 0.4f;
+        } else {
+            contentAlpha = 1.0f;
+        }
+        mPublicLayout.setAlpha(contentAlpha);
+        mPrivateLayout.setAlpha(contentAlpha);
+        mPublicLayout.setTranslationY(translationY);
+        mPrivateLayout.setTranslationY(translationY);
+        if (mChildrenContainer != null) {
+            mChildrenContainer.setAlpha(contentAlpha);
+            mChildrenContainer.setTranslationY(translationY);
+            // TODO: handle children fade out better
+        }
+    }
+
+    private void updateIconVisibilities() {
+        boolean visible = isChildInGroup() || isBelowSpeedBump() || mIconsVisible;
+        mPublicLayout.setIconsVisible(visible);
+        mPrivateLayout.setIconsVisible(visible);
+        if (mChildrenContainer != null) {
+            mChildrenContainer.setIconsVisible(visible);
+        }
+    }
+
+    /**
+     * Get the relative top padding of a view relative to this view. This recursively walks up the
+     * hierarchy and does the corresponding measuring.
+     *
+     * @param view the view to the the padding for. The requested view has to be a child of this
+     *             notification.
+     * @return the toppadding
+     */
+    public int getRelativeTopPadding(View view) {
+        int topPadding = 0;
+        while (view.getParent() instanceof ViewGroup) {
+            topPadding += view.getTop();
+            view = (View) view.getParent();
+            if (view instanceof ExpandableNotificationRow) {
+                return topPadding;
+            }
+        }
+        return topPadding;
+    }
+
     public interface ExpansionLogger {
         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
     }
@@ -804,6 +932,8 @@
         mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height);
         mIncreasedPaddingBetweenElements = getResources()
                 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
+        mIconTransformContentShiftNoIcon = getResources().getDimensionPixelSize(
+                R.dimen.notification_icon_transform_content_shift);
     }
 
     /**
@@ -1276,6 +1406,21 @@
         if (mSettingsIconRow != null) {
             mSettingsIconRow.updateVerticalLocation();
         }
+        updateContentShiftHeight();
+    }
+
+    /**
+     * Updates the content shift height such that the header is completely hidden when coming from
+     * the top.
+     */
+    private void updateContentShiftHeight() {
+        NotificationHeaderView notificationHeader = getNotificationHeader();
+        if (notificationHeader != null) {
+            CachingIconView icon = notificationHeader.getIcon();
+            mIconTransformContentShift = getRelativeTopPadding(icon) + icon.getHeight();
+        } else {
+            mIconTransformContentShift = mIconTransformContentShiftNoIcon;
+        }
     }
 
     private void updateMaxHeights() {
@@ -1532,6 +1677,16 @@
         }
     }
 
+    @Override
+    public void setClipBottomAmount(int clipBottomAmount) {
+        super.setClipBottomAmount(clipBottomAmount);
+        mPrivateLayout.setClipBottomAmount(clipBottomAmount);
+        mPublicLayout.setClipBottomAmount(clipBottomAmount);
+        if (mGuts != null) {
+            mGuts.setClipBottomAmount(clipBottomAmount);
+        }
+    }
+
     public boolean isMaxExpandHeightInitialized() {
         return mMaxExpandHeight != 0;
     }
@@ -1679,4 +1834,57 @@
     public interface OnExpandClickListener {
         void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded);
     }
+
+    @Override
+    public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
+        return new NotificationViewState(stackScrollState);
+    }
+
+    @Override
+    public boolean isAboveShelf() {
+        return mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf);
+    }
+
+    public void setAboveShelf(boolean aboveShelf) {
+        mAboveShelf = aboveShelf;
+    }
+
+    public class NotificationViewState extends ExpandableViewState {
+
+        private final StackScrollState mOverallState;
+
+
+        private NotificationViewState(StackScrollState stackScrollState) {
+            mOverallState = stackScrollState;
+        }
+
+        @Override
+        public void applyToView(View view) {
+            super.applyToView(view);
+            if (view instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+                if (this.isBottomClipped) {
+                    row.setClipToActualHeight(true);
+                }
+                row.applyChildrenState(mOverallState);
+            }
+        }
+
+        @Override
+        protected void onYTranslationAnimationFinished() {
+            super.onYTranslationAnimationFinished();
+            if (mHeadsupDisappearRunning) {
+                setHeadsUpAnimatingAway(false);
+            }
+        }
+
+        @Override
+        public void animateTo(View child, AnimationProperties properties) {
+            super.animateTo(child, properties);
+            if (child instanceof ExpandableNotificationRow) {
+                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+                row.startChildAnimation(mOverallState, properties);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
index 9d9f3b9..4b95f07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -97,15 +97,23 @@
         if (mCustomOutline) {
             return;
         }
-        boolean hasOutline = true;
-        if (isChildInGroup()) {
-            hasOutline = isGroupExpanded() && !isGroupExpansionChanging();
-        } else if (isSummaryWithChildren()) {
-            hasOutline = !isGroupExpanded() || isGroupExpansionChanging();
-        }
+        boolean hasOutline = needsOutline();
         setOutlineProvider(hasOutline ? mProvider : null);
     }
 
+    /**
+     * @return whether the view currently needs an outline. This is usually false in case it doesn't
+     * have a background.
+     */
+    protected boolean needsOutline() {
+        if (isChildInGroup()) {
+            return isGroupExpanded() && !isGroupExpansionChanging();
+        } else if (isSummaryWithChildren()) {
+            return !isGroupExpanded() || isGroupExpansionChanging();
+        }
+        return true;
+    }
+
     public boolean isOutlineShowing() {
         ViewOutlineProvider op = getOutlineProvider();
         return op != null;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index 83b0ee0..0f5981b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -25,6 +25,8 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.stack.ExpandableViewState;
+import com.android.systemui.statusbar.stack.StackScrollState;
 
 import java.util.ArrayList;
 
@@ -36,6 +38,7 @@
     protected OnHeightChangedListener mOnHeightChangedListener;
     private int mActualHeight;
     protected int mClipTopAmount;
+    private float mClipBottomAmount;
     private boolean mDark;
     private ArrayList<View> mMatchParentViews = new ArrayList<View>();
     private static Rect mClipRect = new Rect();
@@ -44,6 +47,8 @@
     private boolean mClipToActualHeight = true;
     private boolean mChangingPosition = false;
     private ViewGroup mTransientContainer;
+    private boolean mInShelf;
+    private boolean mTransformingInShelf;
 
     public ExpandableView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -220,10 +225,26 @@
         updateClipping();
     }
 
+    /**
+     * Set the amount the the notification is clipped on the bottom in addition to the regular
+     * clipping. This is mainly used to clip something in a non-animated way without changing the
+     * actual height of the notification and is purely visual.
+     *
+     * @param clipBottomAmount the amount to clip.
+     */
+    public void setClipBottomAmount(int clipBottomAmount) {
+        mClipBottomAmount = clipBottomAmount;
+        updateClipping();
+    }
+
     public int getClipTopAmount() {
         return mClipTopAmount;
     }
 
+    public float getClipBottomAmount() {
+        return mClipBottomAmount;
+    }
+
     public void setOnHeightChangedListener(OnHeightChangedListener listener) {
         mOnHeightChangedListener = listener;
     }
@@ -261,9 +282,18 @@
 
     public abstract void performAddAnimation(long delay, long duration);
 
+    /**
+     * Set the notification appearance to be below the speed bump.
+     * @param below true if it is below.
+     */
     public void setBelowSpeedBump(boolean below) {
     }
 
+    public int getPinnedHeadsUpHeight() {
+        return getIntrinsicHeight();
+    }
+
+
     /**
      * Sets the translation of the view.
      */
@@ -327,7 +357,8 @@
             if (top >= getActualHeight()) {
                 top = getActualHeight() - 1;
             }
-            mClipRect.set(0, top, getWidth(), getActualHeight() + getExtraBottomPadding());
+            mClipRect.set(0, top, getWidth(), (int) (getActualHeight() + getExtraBottomPadding()
+                                - mClipBottomAmount));
             setClipBounds(mClipRect);
         } else {
             setClipBounds(null);
@@ -438,6 +469,46 @@
 
     public void setActualHeightAnimating(boolean animating) {}
 
+    public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
+        return new ExpandableViewState();
+    }
+
+    /**
+     * @return whether the current view doesn't add height to the overall content. This means that
+     * if it is added to a list of items, it's content will still have the same height.
+     * An example is the notification shelf, that is always placed on top of another view.
+     */
+    public boolean hasNoContentHeight() {
+        return false;
+    }
+
+    /**
+     * @param inShelf whether the view is currently fully in the notification shelf.
+     */
+    public void setInShelf(boolean inShelf) {
+        mInShelf = inShelf;
+    }
+
+    public boolean isInShelf() {
+        return mInShelf;
+    }
+
+    /**
+     * @param transformingInShelf whether the view is currently transforming into the shelf in an
+     *                            animated way
+     */
+    public void setTransformingInShelf(boolean transformingInShelf) {
+        mTransformingInShelf = transformingInShelf;
+    }
+
+    public boolean isTransformingIntoShelf() {
+        return mTransformingInShelf;
+    }
+
+    public boolean isAboveShelf() {
+        return false;
+    }
+
     /**
      * A listener notifying when {@link #getActualHeight} changes.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index 8688c28..dea9e31f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -33,6 +33,7 @@
     private Drawable mBackground;
     private int mClipTopAmount;
     private int mActualHeight;
+    private int mClipBottomAmount;
 
     public NotificationBackgroundView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -44,8 +45,9 @@
     }
 
     private void draw(Canvas canvas, Drawable drawable) {
-        if (drawable != null && mActualHeight > mClipTopAmount) {
-            drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight);
+        int bottom = mActualHeight - mClipBottomAmount;
+        if (drawable != null && bottom > mClipTopAmount) {
+            drawable.setBounds(0, mClipTopAmount, getWidth(), bottom);
             drawable.draw(canvas);
         }
     }
@@ -120,6 +122,11 @@
         invalidate();
     }
 
+    public void setClipBottomAmount(int clipBottomAmount) {
+        mClipBottomAmount = clipBottomAmount;
+        invalidate();
+    }
+
     @Override
     public boolean hasOverlappingRendering() {
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index 58d57f6..ad6a5db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -121,7 +121,9 @@
 
     private int mContentHeightAtAnimationStart = UNDEFINED;
     private boolean mFocusOnVisibilityChange;
-    private boolean mHeadsupDisappearRunning;
+    private boolean mHeadsUpAnimatingAway;
+    private boolean mIconsVisible;
+    private int mClipBottomAmount;
 
 
     public NotificationContentView(Context context, AttributeSet attrs) {
@@ -456,7 +458,7 @@
                     isTransitioningFromTo(VISIBLE_TYPE_HEADSUP, VISIBLE_TYPE_EXPANDED) ||
                     isTransitioningFromTo(VISIBLE_TYPE_EXPANDED, VISIBLE_TYPE_HEADSUP);
             boolean pinned = !isVisibleOrTransitioning(VISIBLE_TYPE_CONTRACTED)
-                    && (mIsHeadsUp || mHeadsupDisappearRunning);
+                    && (mIsHeadsUp || mHeadsUpAnimatingAway);
             if (transitioningBetweenHunAndExpanded || pinned) {
                 return Math.min(mHeadsUpChild.getHeight(), mExpandedChild.getHeight());
             }
@@ -587,9 +589,24 @@
         updateClipping();
     }
 
+
+    public void setClipBottomAmount(int clipBottomAmount) {
+        mClipBottomAmount = clipBottomAmount;
+        updateClipping();
+    }
+
+    @Override
+    public void setTranslationY(float translationY) {
+        super.setTranslationY(translationY);
+        updateClipping();
+    }
+
     private void updateClipping() {
         if (mClipToActualHeight) {
-            mClipBounds.set(0, mClipTopAmount, getWidth(), mContentHeight);
+            int top = (int) (mClipTopAmount - getTranslationY());
+            int bottom = (int) (mContentHeight - mClipBottomAmount - getTranslationY());
+            bottom = Math.max(top, bottom);
+            mClipBounds.set(0, top, getWidth(), bottom);
             setClipBounds(mClipBounds);
         } else {
             setClipBounds(null);
@@ -840,7 +857,7 @@
             return VISIBLE_TYPE_SINGLELINE;
         }
 
-        if ((mIsHeadsUp || mHeadsupDisappearRunning) && mHeadsUpChild != null) {
+        if ((mIsHeadsUp || mHeadsUpAnimatingAway) && mHeadsUpChild != null) {
             if (viewHeight <= mHeadsUpChild.getHeight() || noExpandedChild) {
                 return VISIBLE_TYPE_HEADSUP;
             } else {
@@ -1183,12 +1200,38 @@
         }
     }
 
-    public void setHeadsupDisappearRunning(boolean headsupDisappearRunning) {
-        mHeadsupDisappearRunning = headsupDisappearRunning;
+    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        mHeadsUpAnimatingAway = headsUpAnimatingAway;
         selectLayout(false /* animate */, true /* force */);
     }
 
     public void setFocusOnVisibilityChange() {
         mFocusOnVisibilityChange = true;
     }
+
+    public void setIconsVisible(boolean iconsVisible) {
+        mIconsVisible = iconsVisible;
+        updateIconVisibilities();
+    }
+
+    private void updateIconVisibilities() {
+        if (mContractedWrapper != null) {
+            NotificationHeaderView header = mContractedWrapper.getNotificationHeader();
+            if (header != null) {
+                header.getIcon().setForceHidden(!mIconsVisible);
+            }
+        }
+        if (mHeadsUpWrapper != null) {
+            NotificationHeaderView header = mHeadsUpWrapper.getNotificationHeader();
+            if (header != null) {
+                header.getIcon().setForceHidden(!mIconsVisible);
+            }
+        }
+        if (mExpandedWrapper != null) {
+            NotificationHeaderView header = mExpandedWrapper.getNotificationHeader();
+            if (header != null) {
+                header.getIcon().setForceHidden(!mIconsVisible);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 7019880..3687f6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -19,6 +19,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
+import android.graphics.drawable.Icon;
 import android.os.SystemClock;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
@@ -26,8 +27,11 @@
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.view.View;
+import android.widget.ImageView;
 import android.widget.RemoteViews;
 
+import com.android.internal.statusbar.StatusBarIcon;
+import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -48,9 +52,11 @@
     public static final class Entry {
         private static final long LAUNCH_COOLDOWN = 2000;
         private static final long NOT_LAUNCHED_YET = -LAUNCH_COOLDOWN;
+        private static final int COLOR_INVALID = 1;
         public String key;
         public StatusBarNotification notification;
         public StatusBarIconView icon;
+        public StatusBarIconView expandedIcon;
         public ExpandableNotificationRow row; // the outer expanded view
         private boolean interruption;
         public boolean autoRedacted; // whether the redacted notification was generated by us
@@ -62,11 +68,12 @@
         public RemoteViews cachedHeadsUpContentView;
         public RemoteViews cachedPublicContentView;
         public CharSequence remoteInputText;
+        private int mCachedContrastColor = COLOR_INVALID;
+        private int mCachedContrastColorIsFor = COLOR_INVALID;
 
-        public Entry(StatusBarNotification n, StatusBarIconView ic) {
+        public Entry(StatusBarNotification n) {
             this.key = n.getKey();
             this.notification = n;
-            this.icon = ic;
         }
 
         public void setInterruption() {
@@ -165,6 +172,85 @@
         public boolean hasJustLaunchedFullScreenIntent() {
             return SystemClock.elapsedRealtime() < lastFullScreenIntentLaunchTime + LAUNCH_COOLDOWN;
         }
+
+        /**
+         * Create the icons for a notification
+         * @param context the context to create the icons with
+         * @param sbn the notification
+         * @throws IconException
+         */
+        public void createIcons(Context context, StatusBarNotification sbn) throws IconException {
+            Notification n = sbn.getNotification();
+            final Icon smallIcon = n.getSmallIcon();
+            if (smallIcon == null) {
+                throw new IconException("No small icon in notification from "
+                        + sbn.getPackageName());
+            }
+
+            // Construct the icon.
+            icon = new StatusBarIconView(context,
+                    sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
+            icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+
+            // Construct the expanded icon.
+            expandedIcon = new StatusBarIconView(context,
+                    sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), n);
+            expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
+            final StatusBarIcon ic = new StatusBarIcon(
+                    sbn.getUser(),
+                    sbn.getPackageName(),
+                    smallIcon,
+                    n.iconLevel,
+                    n.number,
+                    StatusBarIconView.contentDescForNotification(context, n));
+            if (!icon.set(ic) || !expandedIcon.set(ic)) {
+                icon = null;
+                expandedIcon = null;
+                throw new IconException("Couldn't create icon: " + ic);
+            }
+        }
+
+        public void setIconTag(int key, Object tag) {
+            if (icon != null) {
+                icon.setTag(key, tag);
+                expandedIcon.setTag(key, tag);
+            }
+        }
+
+        /**
+         * Update the notification icons.
+         * @param context the context to create the icons with.
+         * @param n the notification to read the icon from.
+         * @throws IconException
+         */
+        public void updateIcons(Context context, Notification n) throws IconException {
+            if (icon != null) {
+                // Update the icon
+                final StatusBarIcon ic = new StatusBarIcon(
+                        notification.getUser(),
+                        notification.getPackageName(),
+                        n.getSmallIcon(),
+                        n.iconLevel,
+                        n.number,
+                        StatusBarIconView.contentDescForNotification(context, n));
+                icon.setNotification(n);
+                expandedIcon.setNotification(n);
+                if (!icon.set(ic) || !expandedIcon.set(ic)) {
+                    throw new IconException("Couldn't update icon: " + ic);
+                }
+            }
+        }
+
+        public int getContrastedColor(Context context) {
+            int rawColor = notification.getNotification().color;
+            if (mCachedContrastColorIsFor == rawColor && mCachedContrastColor != COLOR_INVALID) {
+                return mCachedContrastColor;
+            }
+            final int contrasted = NotificationColorUtil.resolveContrastColor(context, rawColor);
+            mCachedContrastColorIsFor = rawColor;
+            mCachedContrastColor = contrasted;
+            return mCachedContrastColor;
+        }
     }
 
     private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
@@ -472,4 +558,10 @@
         public String getCurrentMediaNotificationKey();
         public NotificationGroupManager getGroupManager();
     }
+
+    public static class IconException extends Exception {
+        IconException(String error) {
+            super(error);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index bb327ef..ed1179a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -62,6 +62,7 @@
 
     private Drawable mBackground;
     private int mClipTopAmount;
+    private int mClipBottomAmount;
     private int mActualHeight;
     private boolean mExposed;
     private INotificationManager mINotificationManager;
@@ -136,8 +137,10 @@
     }
 
     private void draw(Canvas canvas, Drawable drawable) {
-        if (drawable != null) {
-            drawable.setBounds(0, mClipTopAmount, getWidth(), mActualHeight);
+        int top = mClipTopAmount;
+        int bottom = mActualHeight - mClipBottomAmount;
+        if (drawable != null && top < bottom) {
+            drawable.setBounds(0, top, getWidth(), bottom);
             drawable.draw(canvas);
         }
     }
@@ -423,6 +426,11 @@
         invalidate();
     }
 
+    public void setClipBottomAmount(int clipBottomAmount) {
+        mClipBottomAmount = clipBottomAmount;
+        invalidate();
+    }
+
     @Override
     public boolean hasOverlappingRendering() {
         // Prevents this view from creating a layer when alpha is animating.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
deleted file mode 100644
index 8e8ce1a..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowContainer.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2014 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.statusbar;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.TextView;
-
-import com.android.systemui.R;
-import com.android.systemui.ViewInvertHelper;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-
-/**
- * Container view for overflowing notification icons on Keyguard.
- */
-public class NotificationOverflowContainer extends ActivatableNotificationView {
-
-    private NotificationOverflowIconsView mIconsView;
-    private ViewInvertHelper mViewInvertHelper;
-    private boolean mDark;
-    private View mContent;
-
-    public NotificationOverflowContainer(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mIconsView = (NotificationOverflowIconsView) findViewById(R.id.overflow_icons_view);
-        mIconsView.setMoreText((TextView) findViewById(R.id.more_text));
-        mIconsView.setOverflowIndicator(findViewById(R.id.more_icon_overflow));
-        mContent = findViewById(R.id.content);
-        mViewInvertHelper = new ViewInvertHelper(mContent,
-                NotificationPanelView.DOZE_ANIMATION_DURATION);
-    }
-
-    @Override
-    public void setDark(boolean dark, boolean fade, long delay) {
-        super.setDark(dark, fade, delay);
-        if (mDark == dark) return;
-        mDark = dark;
-        if (fade) {
-            mViewInvertHelper.fade(dark, delay);
-        } else {
-            mViewInvertHelper.update(dark);
-        }
-    }
-
-    @Override
-    protected View getContentView() {
-        return mContent;
-    }
-
-    public NotificationOverflowIconsView getIconsView() {
-        return mIconsView;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
deleted file mode 100644
index 88bb714..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2014 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.statusbar;
-
-import android.app.Notification;
-import android.content.Context;
-import android.graphics.PorterDuff;
-import android.util.AttributeSet;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-import com.android.internal.util.NotificationColorUtil;
-import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.IconMerger;
-
-/**
- * A view to display all the overflowing icons on Keyguard.
- */
-public class NotificationOverflowIconsView extends IconMerger {
-
-    private TextView mMoreText;
-    private int mTintColor;
-    private int mIconSize;
-    private NotificationColorUtil mNotificationColorUtil;
-
-    public NotificationOverflowIconsView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-        mNotificationColorUtil = NotificationColorUtil.getInstance(getContext());
-        mTintColor = getContext().getColor(R.color.keyguard_overflow_content_color);
-        mIconSize = getResources().getDimensionPixelSize(
-                com.android.internal.R.dimen.status_bar_icon_size);
-    }
-
-    public void setMoreText(TextView moreText) {
-        mMoreText = moreText;
-    }
-
-    public void addNotification(NotificationData.Entry notification) {
-        StatusBarIconView v = new StatusBarIconView(getContext(), "",
-                notification.notification.getNotification());
-        v.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
-        addView(v, mIconSize, mIconSize);
-        v.set(notification.icon.getStatusBarIcon());
-        applyColor(notification.notification.getNotification(), v);
-        updateMoreText();
-    }
-
-    private void applyColor(Notification notification, StatusBarIconView view) {
-        view.setColorFilter(mTintColor, PorterDuff.Mode.MULTIPLY);
-    }
-
-    private void updateMoreText() {
-        mMoreText.setText(
-                getResources().getString(R.string.keyguard_more_overflow_text, getChildCount()));
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
new file mode 100644
index 0000000..6b9a89ef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -0,0 +1,469 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.ViewInvertHelper;
+import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.phone.NotificationIconContainer;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.stack.AmbientState;
+import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.statusbar.stack.ExpandableViewState;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.stack.StackScrollState;
+
+import java.util.ArrayList;
+import java.util.WeakHashMap;
+
+/**
+ * A notification shelf view that is placed inside the notification scroller. It manages the
+ * overflow icons that don't fit into the regular list anymore.
+ */
+public class NotificationShelf extends ActivatableNotificationView {
+
+    private ViewInvertHelper mViewInvertHelper;
+    private boolean mDark;
+    private NotificationIconContainer mShelfIcons;
+    private ArrayList<StatusBarIconView> mIcons = new ArrayList<>();
+    private ShelfState mShelfState;
+    private int[] mTmp = new int[2];
+    private boolean mHideBackground;
+    private int mIconAppearTopPadding;
+    private int mStatusBarHeight;
+    private int mStatusBarPaddingStart;
+    private AmbientState mAmbientState;
+    private NotificationStackScrollLayout mHostLayout;
+    private int mMaxLayoutHeight;
+    private int mPaddingBetweenElements;
+    private int mNotGoneIndex;
+    private boolean mHasItemsInStableShelf;
+    private NotificationIconContainer mCollapsedIcons;
+
+    public NotificationShelf(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mShelfIcons = (NotificationIconContainer) findViewById(R.id.content);
+        mShelfIcons.setClipChildren(false);
+        mShelfIcons.setClipToPadding(false);
+
+        setClipToActualHeight(false);
+        setClipChildren(false);
+        setClipToPadding(false);
+        mShelfIcons.setShowAllIcons(false);
+        mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
+                NotificationPanelView.DOZE_ANIMATION_DURATION);
+        mShelfState = new ShelfState();
+        initDimens();
+    }
+
+    public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout) {
+        mAmbientState = ambientState;
+        mHostLayout = hostLayout;
+    }
+
+    private void initDimens() {
+        mIconAppearTopPadding = getResources().getDimensionPixelSize(
+                R.dimen.notification_icon_appear_padding);
+        mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
+        mStatusBarPaddingStart = getResources().getDimensionPixelOffset(
+                R.dimen.status_bar_padding_start);
+        mPaddingBetweenElements = getResources().getDimensionPixelSize(
+                R.dimen.notification_divider_height);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        initDimens();
+    }
+
+    @Override
+    public void setDark(boolean dark, boolean fade, long delay) {
+        super.setDark(dark, fade, delay);
+        if (mDark == dark) return;
+        mDark = dark;
+        if (fade) {
+            mViewInvertHelper.fade(dark, delay);
+        } else {
+            mViewInvertHelper.update(dark);
+        }
+    }
+
+    @Override
+    protected View getContentView() {
+        return mShelfIcons;
+    }
+
+    public NotificationIconContainer getShelfIcons() {
+        return mShelfIcons;
+    }
+
+    @Override
+    public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
+        return mShelfState;
+    }
+
+    public void updateState(StackScrollState resultState,
+            AmbientState ambientState) {
+        View lastView = ambientState.getLastVisibleBackgroundChild();
+        if (lastView != null) {
+            float maxShelfEnd = ambientState.getInnerHeight() + ambientState.getTopPadding()
+                    + ambientState.getStackTranslation();
+            ExpandableViewState lastViewState = resultState.getViewStateForView(lastView);
+            float viewEnd = lastViewState.yTranslation + lastViewState.height;
+            mShelfState.copyFrom(lastViewState);
+            mShelfState.height = getIntrinsicHeight();
+            mShelfState.yTranslation = Math.max(Math.min(viewEnd, maxShelfEnd) - mShelfState.height,
+                    getFullyClosedTranslation());
+            mShelfState.zTranslation = ambientState.getBaseZHeight();
+            float openedAmount = (mShelfState.yTranslation - getFullyClosedTranslation())
+                    / (getIntrinsicHeight() * 2);
+            openedAmount = Math.min(1.0f, openedAmount);
+            mShelfState.openedAmount = openedAmount;
+            mShelfState.clipTopAmount = 0;
+            mShelfState.alpha = 1.0f;
+            mShelfState.belowSpeedBump = mAmbientState.getSpeedBumpIndex() == 0;
+            mShelfState.shadowAlpha = 1.0f;
+            mShelfState.isBottomClipped = false;
+            mShelfState.hideSensitive = false;
+            mShelfState.xTranslation = getTranslationX();
+            if (mNotGoneIndex != -1) {
+                mShelfState.notGoneIndex = Math.min(mShelfState.notGoneIndex, mNotGoneIndex);
+            }
+            mShelfState.hasItemsInStableShelf = lastViewState.inShelf;
+        } else {
+            mShelfState.hidden = true;
+            mShelfState.location = ExpandableViewState.LOCATION_GONE;
+            mShelfState.hasItemsInStableShelf = false;
+        }
+    }
+
+    /**
+     * Update the shelf appearance based on the other notifications around it. This transforms
+     * the icons from the notification area into the shelf.
+     */
+    public void updateAppearance() {
+        WeakHashMap<View, NotificationIconContainer.IconState> iconStates =
+                mShelfIcons.resetViewStates();
+        float numViewsInShelf = 0.0f;
+        View lastChild = mAmbientState.getLastVisibleBackgroundChild();
+        mNotGoneIndex = -1;
+        float interpolationStart = mMaxLayoutHeight - getIntrinsicHeight() * 2;
+        float expandAmount = 0.0f;
+        if (getTranslationY() >= interpolationStart) {
+            expandAmount = (getTranslationY() - interpolationStart) / getIntrinsicHeight();
+            expandAmount = Math.min(1.0f, expandAmount);
+        }
+        //  find the first view that doesn't overlap with the shelf
+        int notificationIndex = 0;
+        int notGoneIndex = 0;
+        int colorOfViewBeforeLast = 0;
+        boolean backgroundForceHidden = false;
+        if (mHideBackground && !mShelfState.hasItemsInStableShelf) {
+            backgroundForceHidden = true;
+        }
+        int colorTwoBefore = NO_COLOR;
+        int previousColor = NO_COLOR;
+        float transitionAmount = 0.0f;
+        while (notificationIndex < mHostLayout.getChildCount()) {
+            ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
+            notificationIndex++;
+            if (!(child instanceof ExpandableNotificationRow)
+                    || child.getVisibility() == GONE) {
+                continue;
+            }
+            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            StatusBarIconView icon = row.getEntry().expandedIcon;
+            NotificationIconContainer.IconState iconState = iconStates.get(icon);
+            float notificationClipEnd;
+            float shelfStart = getTranslationY();
+            boolean aboveShelf = row.getTranslationZ() > mAmbientState.getBaseZHeight();
+            boolean isLastChild = child == lastChild;
+            if (isLastChild || aboveShelf || backgroundForceHidden) {
+                notificationClipEnd = shelfStart + getIntrinsicHeight();
+            } else {
+                notificationClipEnd = shelfStart - mPaddingBetweenElements;
+                float height = notificationClipEnd - row.getTranslationY();
+                if (!row.isBelowSpeedBump() && height <= getNotificationMergeSize()) {
+                    // We want the gap to close when we reached the minimum size and only shrink
+                    // before
+                    notificationClipEnd = Math.min(shelfStart,
+                            row.getTranslationY() + getNotificationMergeSize());
+                }
+            }
+            updateNotificationClipHeight(row, notificationClipEnd);
+            float inShelfAmount = updateIconAppearance(row, iconState, icon, expandAmount,
+                    isLastChild);
+            numViewsInShelf += inShelfAmount;
+            int ownColorUntinted = row.getBackgroundColorWithoutTint();
+            if (row.getTranslationY() >= getTranslationY() && mNotGoneIndex == -1) {
+                mNotGoneIndex = notGoneIndex;
+                setTintColor(previousColor);
+                setOverrideTintColor(colorTwoBefore, transitionAmount);
+
+            } else if (mNotGoneIndex == -1) {
+                colorTwoBefore = previousColor;
+                transitionAmount = inShelfAmount;
+            }
+            if (isLastChild && colorOfViewBeforeLast != NO_COLOR) {
+                row.setOverrideTintColor(colorOfViewBeforeLast, inShelfAmount);
+            } else {
+                colorOfViewBeforeLast = ownColorUntinted;
+                row.setOverrideTintColor(NO_COLOR, 0 /* overrideAmount */);
+            }
+            if (notGoneIndex != 0 || !aboveShelf) {
+                row.setAboveShelf(false);
+            }
+            notGoneIndex++;
+            previousColor = ownColorUntinted;
+        }
+        mShelfIcons.calculateIconTranslations();
+        mShelfIcons.applyIconStates();
+        setVisibility(numViewsInShelf != 0.0f && mAmbientState.isShadeExpanded()
+                ? VISIBLE
+                : INVISIBLE);
+        boolean hideBackground = numViewsInShelf < 1.0f;
+        setHideBackground(hideBackground || backgroundForceHidden);
+        if (mNotGoneIndex == -1) {
+            mNotGoneIndex = notGoneIndex;
+        }
+    }
+
+    private void updateNotificationClipHeight(ExpandableNotificationRow row,
+            float notificationClipEnd) {
+        float viewEnd = row.getTranslationY() + row.getActualHeight();
+        if (viewEnd > notificationClipEnd
+                && (mAmbientState.isShadeExpanded()
+                        || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
+            row.setClipBottomAmount((int) (viewEnd - notificationClipEnd));
+        } else {
+            row.setClipBottomAmount(0);
+        }
+    }
+
+    /**
+     * @return the icon amount how much this notification is in the shelf;
+     */
+    private float updateIconAppearance(ExpandableNotificationRow row,
+            NotificationIconContainer.IconState iconState, StatusBarIconView icon,
+            float expandAmount, boolean isLastChild) {
+        // Let calculate how much the view is in the shelf
+        float viewStart = row.getTranslationY();
+        int transformHeight = row.getActualHeight() + mPaddingBetweenElements;
+        if (isLastChild) {
+            transformHeight =
+                    Math.min(transformHeight, row.getMinHeight() - getIntrinsicHeight());
+        }
+        float viewEnd = viewStart + transformHeight;
+        float iconAppearAmount;
+        float yTranslation;
+        float alpha = 1.0f;
+        if (viewEnd >= getTranslationY() && (mAmbientState.isShadeExpanded()
+                || (!row.isPinned() && !row.isHeadsUpAnimatingAway()))) {
+            if (viewStart < getTranslationY()) {
+                float linearAmount = (getTranslationY() - viewStart) / transformHeight;
+                float interpolatedAmount =  Interpolators.ACCELERATE_DECELERATE.getInterpolation(
+                        linearAmount);
+                interpolatedAmount = NotificationUtils.interpolate(
+                        interpolatedAmount, linearAmount, expandAmount);
+                iconAppearAmount = 1.0f - interpolatedAmount;
+            } else {
+                iconAppearAmount = 1.0f;
+            }
+        } else {
+            iconAppearAmount = 0.0f;
+        }
+
+        // Lets now calculate how much of the transformation has already happened. This is different
+        // from the above, since we only start transforming when the view is already quite a bit
+        // pushed in.
+        View rowIcon = row.getNotificationIcon();
+        float notificationIconPosition = viewStart;
+        float notificationIconSize = 0.0f;
+        int iconTopPadding;
+        if (rowIcon != null) {
+            iconTopPadding = row.getRelativeTopPadding(rowIcon);
+            notificationIconSize = rowIcon.getHeight();
+        } else {
+            iconTopPadding = mIconAppearTopPadding;
+        }
+        notificationIconPosition += iconTopPadding;
+        float shelfIconPosition = getTranslationY() + icon.getTop();
+        shelfIconPosition += ((1.0f - icon.getIconScale()) * icon.getHeight()) / 2.0f;
+        float transitionDistance = getIntrinsicHeight() * 1.5f;
+        if (isLastChild) {
+            transitionDistance = Math.min(transitionDistance, row.getMinHeight()
+                    - getIntrinsicHeight());
+        }
+        float transformationStartPosition = getTranslationY() - transitionDistance;
+        float transitionAmount = 0.0f;
+        if (viewStart < transformationStartPosition
+                || (!mAmbientState.isShadeExpanded()
+                        && (row.isPinned() || row.isHeadsUpAnimatingAway()))) {
+            // We simply place it on the icon of the notification
+            yTranslation = notificationIconPosition - shelfIconPosition;
+        } else {
+            transitionAmount = (viewStart - transformationStartPosition)
+                    / transitionDistance;
+            float startPosition = transformationStartPosition + iconTopPadding;
+            yTranslation = NotificationUtils.interpolate(
+                    startPosition - shelfIconPosition, 0, transitionAmount);
+            // If we are merging into the shelf, lets make sure the shelf is at least on our height,
+            // otherwise the icons won't be visible.
+            setTranslationZ(Math.max(getTranslationZ(), row.getTranslationZ()));
+        }
+        float shelfIconSize = icon.getHeight() * icon.getIconScale();
+        if (!row.isShowingIcon()) {
+            // The view currently doesn't have an icon, lets transform it in!
+            alpha = transitionAmount;
+            notificationIconSize = shelfIconSize / 2.0f;
+        }
+        // The notification size is different from the size in the shelf / statusbar
+        float newSize = NotificationUtils.interpolate(notificationIconSize, shelfIconSize,
+                transitionAmount);
+        row.setIconTransformationAmount(transitionAmount, isLastChild);
+        if (iconState != null) {
+            iconState.scaleX = newSize / icon.getHeight() / icon.getIconScale();
+            iconState.scaleY = iconState.scaleX;
+            iconState.hidden = transitionAmount == 0.0f;
+            iconState.iconAppearAmount = iconAppearAmount;
+            iconState.alpha = alpha;
+            iconState.yTranslation = yTranslation;
+            icon.setVisibility(transitionAmount == 0.0f ? INVISIBLE : VISIBLE);
+            if (row.isInShelf() && !row.isTransformingIntoShelf()) {
+                iconState.iconAppearAmount = 1.0f;
+                iconState.alpha = 1.0f;
+                iconState.scaleX = 1.0f;
+                iconState.scaleY = 1.0f;
+                iconState.hidden = false;
+            }
+        }
+        return iconAppearAmount;
+    }
+
+    private float getFullyClosedTranslation() {
+        return - (getIntrinsicHeight() - mStatusBarHeight) / 2;
+    }
+
+    public int getNotificationMergeSize() {
+        return getIntrinsicHeight();
+    }
+
+    @Override
+    public boolean hasNoContentHeight() {
+        return true;
+    }
+
+    private void setHideBackground(boolean hideBackground) {
+        mHideBackground = hideBackground;
+        updateBackground();
+        updateOutline();
+    }
+
+    public boolean hidesBackground() {
+        return mHideBackground;
+    }
+
+    @Override
+    protected boolean needsOutline() {
+        return !mHideBackground && super.needsOutline();
+    }
+
+    @Override
+    protected boolean shouldHideBackground() {
+        return super.shouldHideBackground() || mHideBackground;
+    }
+
+    private void setOpenedAmount(float openedAmount) {
+        mCollapsedIcons.getLocationOnScreen(mTmp);
+        int start = mTmp[0];
+        if (isLayoutRtl()) {
+            start = getWidth() - start - mCollapsedIcons.getWidth();
+        }
+        int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(),
+                mShelfIcons.getWidth(),
+                openedAmount);
+        mShelfIcons.setActualLayoutWidth(width);
+        float padding = NotificationUtils.interpolate(mCollapsedIcons.getPaddingEnd(),
+                mShelfIcons.getPaddingEnd(),
+                openedAmount);
+        mShelfIcons.setActualPaddingEnd(padding);
+        float paddingStart = NotificationUtils.interpolate(start,
+                mShelfIcons.getPaddingStart(), openedAmount);
+        mShelfIcons.setActualPaddingStart(paddingStart);
+    }
+
+    public void setMaxLayoutHeight(int maxLayoutHeight) {
+        mMaxLayoutHeight = maxLayoutHeight;
+    }
+
+    /**
+     * @return the index of the notification at which the shelf visually resides
+     */
+    public int getNotGoneIndex() {
+        return mNotGoneIndex;
+    }
+
+    private void setHasItemsInStableShelf(boolean hasItemsInStableShelf) {
+        mHasItemsInStableShelf = hasItemsInStableShelf;
+    }
+
+    /**
+     * @return whether the shelf has any icons in it when a potential animation has finished, i.e
+     *         if the current state would be applied right now
+     */
+    public boolean hasItemsInStableShelf() {
+        return mHasItemsInStableShelf;
+    }
+
+    public void setCollapsedIcons(NotificationIconContainer collapsedIcons) {
+        mCollapsedIcons = collapsedIcons;
+    }
+
+    private class ShelfState extends ExpandableViewState {
+        private float openedAmount;
+        private boolean hasItemsInStableShelf;
+
+        @Override
+        public void applyToView(View view) {
+            super.applyToView(view);
+            updateAppearance();
+            setOpenedAmount(openedAmount);
+            setHasItemsInStableShelf(hasItemsInStableShelf);
+        }
+
+        @Override
+        public void animateTo(View child, AnimationProperties properties) {
+            super.animateTo(child, properties);
+            setOpenedAmount(openedAmount);
+            updateAppearance();
+            setHasItemsInStableShelf(hasItemsInStableShelf);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index cdfdad4..d635bb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.statusbar;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.app.Notification;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -30,20 +33,55 @@
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.FloatProperty;
 import android.util.Log;
+import android.util.Property;
 import android.util.TypedValue;
 import android.view.ViewDebug;
 import android.view.accessibility.AccessibilityEvent;
+import android.view.animation.Interpolator;
 
 import com.android.internal.statusbar.StatusBarIcon;
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.NotificationUtils;
 
 import java.text.NumberFormat;
 
 public class StatusBarIconView extends AnimatedImageView {
-    private static final String TAG = "StatusBarIconView";
-    private boolean mAlwaysScaleIcon;
+    public static final int STATE_ICON = 0;
+    public static final int STATE_DOT = 1;
+    public static final int STATE_HIDDEN = 2;
 
+    private static final String TAG = "StatusBarIconView";
+    private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
+            = new FloatProperty<StatusBarIconView>("iconAppearAmount") {
+
+        @Override
+        public void setValue(StatusBarIconView object, float value) {
+            object.setIconAppearAmount(value);
+        }
+
+        @Override
+        public Float get(StatusBarIconView object) {
+            return object.getIconAppearAmount();
+        }
+    };
+    private static final Property<StatusBarIconView, Float> DOT_APPEAR_AMOUNT
+            = new FloatProperty<StatusBarIconView>("dot_appear_amount") {
+
+        @Override
+        public void setValue(StatusBarIconView object, float value) {
+            object.setDotAppearAmount(value);
+        }
+
+        @Override
+        public Float get(StatusBarIconView object) {
+            return object.getDotAppearAmount();
+        }
+    };
+
+    private boolean mAlwaysScaleIcon;
     private StatusBarIcon mIcon;
     @ViewDebug.ExportedProperty private String mSlot;
     private Drawable mNumberBackground;
@@ -54,6 +92,16 @@
     private Notification mNotification;
     private final boolean mBlocked;
     private int mDensity;
+    private float mIconScale = 1.0f;
+    private final Paint mDotPaint = new Paint();
+    private boolean mDotVisible;
+    private float mDotRadius;
+    private int mStaticDotRadius;
+    private int mVisibleState = STATE_ICON;
+    private float mIconAppearAmount = 1.0f;
+    private ObjectAnimator mIconAppearAnimator;
+    private ObjectAnimator mDotAnimator;
+    private float mDotAppearAmount;
 
     public StatusBarIconView(Context context, String slot, Notification notification) {
         this(context, slot, notification, false);
@@ -72,6 +120,11 @@
         maybeUpdateIconScale();
         setScaleType(ScaleType.CENTER);
         mDensity = context.getResources().getDisplayMetrics().densityDpi;
+        if (mNotification != null) {
+            setIconTint(getContext().getColor(
+                    com.android.internal.R.color.notification_icon_default_color));
+        }
+        reloadDimens();
     }
 
     private void maybeUpdateIconScale() {
@@ -86,9 +139,11 @@
         Resources res = mContext.getResources();
         final int outerBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
         final int imageBounds = res.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size);
-        final float scale = (float)imageBounds / (float)outerBounds;
-        setScaleX(scale);
-        setScaleY(scale);
+        mIconScale = (float)imageBounds / (float)outerBounds;
+    }
+
+    public float getIconScale() {
+        return mIconScale;
     }
 
     @Override
@@ -99,6 +154,15 @@
             mDensity = density;
             maybeUpdateIconScale();
             updateDrawable();
+            reloadDimens();
+        }
+    }
+
+    private void reloadDimens() {
+        boolean applyRadius = mDotRadius == mStaticDotRadius;
+        mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
+        if (applyRadius) {
+            mDotRadius = mStaticDotRadius;
         }
     }
 
@@ -259,12 +323,32 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
+        if (mIconAppearAmount > 0.0f) {
+            canvas.save();
+            canvas.scale(mIconScale * mIconAppearAmount, mIconScale * mIconAppearAmount,
+                    getWidth() / 2, getHeight() / 2);
+            super.onDraw(canvas);
+            canvas.restore();
+        }
 
         if (mNumberBackground != null) {
             mNumberBackground.draw(canvas);
             canvas.drawText(mNumberText, mNumberX, mNumberY, mNumberPain);
         }
+        if (mDotAppearAmount != 0.0f) {
+            float radius;
+            float alpha;
+            if (mDotAppearAmount <= 1.0f) {
+                radius = mDotRadius * mDotAppearAmount;
+                alpha = 1.0f;
+            } else {
+                float fadeOutAmount = mDotAppearAmount - 1.0f;
+                alpha = 1.0f - fadeOutAmount;
+                radius = NotificationUtils.interpolate(mDotRadius, getWidth() / 4, fadeOutAmount);
+            }
+            mDotPaint.setAlpha((int) (alpha * 255));
+            canvas.drawCircle(getWidth() / 2, getHeight() / 2, radius, mDotPaint);
+        }
     }
 
     @Override
@@ -351,4 +435,97 @@
         return c.getString(R.string.accessibility_desc_notification_icon, appName, desc);
     }
 
+    public void setIconTint(int iconTint) {
+        mDotPaint.setColor(iconTint);
+    }
+
+    public void setVisibleState(int state) {
+        setVisibleState(state, true /* animate */, null /* endRunnable */);
+    }
+
+    public void setVisibleState(int state, boolean animate) {
+        setVisibleState(state, animate, null);
+    }
+
+    @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    public void setVisibleState(int visibleState, boolean animate, Runnable endRunnable) {
+        if (visibleState != mVisibleState) {
+            mVisibleState = visibleState;
+            if (animate) {
+                if (mIconAppearAnimator != null) {
+                    mIconAppearAnimator.cancel();
+                }
+                float targetAmount = 0.0f;
+                Interpolator interpolator = Interpolators.FAST_OUT_LINEAR_IN;
+                if (visibleState == STATE_ICON) {
+                    targetAmount = 1.0f;
+                    interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
+                }
+                mIconAppearAnimator = ObjectAnimator.ofFloat(this, ICON_APPEAR_AMOUNT,
+                        targetAmount);
+                mIconAppearAnimator.setInterpolator(interpolator);
+                mIconAppearAnimator.setDuration(100);
+                mIconAppearAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mIconAppearAnimator = null;
+                        if (endRunnable != null) {
+                            endRunnable.run();
+                        }
+                    }
+                });
+                mIconAppearAnimator.start();
+
+                if (mDotAnimator != null) {
+                    mDotAnimator.cancel();
+                }
+                targetAmount = visibleState == STATE_ICON ? 2.0f : 0.0f;
+                interpolator = Interpolators.FAST_OUT_LINEAR_IN;
+                if (visibleState == STATE_DOT) {
+                    targetAmount = 1.0f;
+                    interpolator = Interpolators.LINEAR_OUT_SLOW_IN;
+                }
+                mDotAnimator = ObjectAnimator.ofFloat(this, DOT_APPEAR_AMOUNT,
+                        targetAmount);
+                mDotAnimator.setInterpolator(interpolator);
+                mDotAnimator.setDuration(100);
+                mDotAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mDotAnimator = null;
+                    }
+                });
+                mDotAnimator.start();
+            } else {
+                setIconAppearAmount(visibleState == STATE_ICON ? 1.0f : 0.0f);
+                setDotAppearAmount(visibleState == STATE_DOT ? 1.0f : 0.0f);
+            }
+        }
+    }
+
+    public void setIconAppearAmount(float iconAppearAmount) {
+        mIconAppearAmount = iconAppearAmount;
+        invalidate();
+    }
+
+    public float getIconAppearAmount() {
+        return mIconAppearAmount;
+    }
+
+    public int getVisibleState() {
+        return mVisibleState;
+    }
+
+    public void setDotAppearAmount(float dotAppearAmount) {
+        mDotAppearAmount = dotAppearAmount;
+        invalidate();
+    }
+
+    public float getDotAppearAmount() {
+        return mDotAppearAmount;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
deleted file mode 100644
index f86badb..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/IconMerger.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2008 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.statusbar.phone;
-
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.util.AttributeSet;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import com.android.systemui.R;
-
-public class IconMerger extends LinearLayout {
-    private static final String TAG = "IconMerger";
-    private static final boolean DEBUG = false;
-
-    private int mIconSize;
-    private int mIconHPadding;
-
-    private View mMoreView;
-
-    public IconMerger(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        reloadDimens();
-        if (DEBUG) {
-            setBackgroundColor(0x800099FF);
-        }
-    }
-
-    private void reloadDimens() {
-        Resources res = mContext.getResources();
-        mIconSize = res.getDimensionPixelSize(R.dimen.status_bar_icon_size);
-        mIconHPadding = res.getDimensionPixelSize(R.dimen.status_bar_icon_padding);
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        reloadDimens();
-    }
-
-    public void setOverflowIndicator(View v) {
-        mMoreView = v;
-    }
-
-    private int getFullIconWidth() {
-        return mIconSize + 2 * mIconHPadding;
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        // we need to constrain this to an integral multiple of our children
-        int width = getMeasuredWidth();
-        setMeasuredDimension(width - (width % getFullIconWidth()), getMeasuredHeight());
-    }
-
-    @Override
-    protected void onLayout(boolean changed, int l, int t, int r, int b) {
-        super.onLayout(changed, l, t, r, b);
-        checkOverflow(r - l);
-    }
-
-    private void checkOverflow(int width) {
-        if (mMoreView == null) return;
-
-        final int N = getChildCount();
-        int visibleChildren = 0;
-        for (int i=0; i<N; i++) {
-            if (getChildAt(i).getVisibility() != GONE) visibleChildren++;
-        }
-        final boolean overflowShown = (mMoreView.getVisibility() == View.VISIBLE);
-        // let's assume we have one more slot if the more icon is already showing
-        if (overflowShown) visibleChildren --;
-        final boolean moreRequired = visibleChildren * getFullIconWidth() > width;
-        if (moreRequired != overflowShown) {
-            post(new Runnable() {
-                @Override
-                public void run() {
-                    mMoreView.setVisibility(moreRequired ? View.VISIBLE : View.GONE);
-                }
-            });
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 784cb48..70beac8ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -80,7 +80,7 @@
         mClockYFractionMin = res.getFraction(R.fraction.keyguard_clock_y_fraction_min, 1, 1);
         mClockYFractionMax = res.getFraction(R.fraction.keyguard_clock_y_fraction_max, 1, 1);
         mMoreCardNotificationAmount =
-                (float) res.getDimensionPixelSize(R.dimen.notification_summary_height) /
+                (float) res.getDimensionPixelSize(R.dimen.notification_shelf_height) /
                         res.getDimensionPixelSize(R.dimen.notification_min_height);
         mDensity = res.getDisplayMetrics().density;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index cbaab14..0dbff19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -8,16 +8,19 @@
 import android.support.annotation.NonNull;
 import android.view.LayoutInflater;
 import android.view.View;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 
 import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
 import java.util.ArrayList;
+import java.util.function.Function;
 
 /**
  * A controller for the space in the status bar to the left of the system icons. This area is
@@ -32,13 +35,16 @@
 
     private PhoneStatusBar mPhoneStatusBar;
     protected View mNotificationIconArea;
-    private IconMerger mNotificationIcons;
-    private ImageView mMoreIcon;
+    private NotificationIconContainer mNotificationIcons;
+    private NotificationIconContainer mShelfIcons;
     private final Rect mTintArea = new Rect();
+    private NotificationStackScrollLayout mNotificationScrollLayout;
+    private Context mContext;
 
     public NotificationIconAreaController(Context context, PhoneStatusBar phoneStatusBar) {
         mPhoneStatusBar = phoneStatusBar;
         mNotificationColorUtil = NotificationColorUtil.getInstance(context);
+        mContext = context;
 
         initializeNotificationAreaViews(context);
     }
@@ -55,15 +61,14 @@
 
         LayoutInflater layoutInflater = LayoutInflater.from(context);
         mNotificationIconArea = inflateIconArea(layoutInflater);
+        mNotificationIcons = (NotificationIconContainer) mNotificationIconArea.findViewById(
+                R.id.notificationIcons);
 
-        mNotificationIcons =
-                (IconMerger) mNotificationIconArea.findViewById(R.id.notificationIcons);
+        NotificationShelf shelf = mPhoneStatusBar.getNotificationShelf();
+        mShelfIcons = shelf.getShelfIcons();
+        shelf.setCollapsedIcons(mNotificationIcons);
 
-        mMoreIcon = (ImageView) mNotificationIconArea.findViewById(R.id.moreIcon);
-        if (mMoreIcon != null) {
-            mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
-            mNotificationIcons.setOverflowIndicator(mMoreIcon);
-        }
+        mNotificationScrollLayout = mPhoneStatusBar.getNotificationScrollLayout();
     }
 
     public void onDensityOrFontScaleChanged(Context context) {
@@ -109,14 +114,10 @@
     }
 
     /**
-     * Sets the color that should be used to tint any icons in the notification area. If this
-     * method is not called, the default tint is {@link Color#WHITE}.
+     * Sets the color that should be used to tint any icons in the notification area.
      */
     public void setIconTint(int iconTint) {
         mIconTint = iconTint;
-        if (mMoreIcon != null) {
-            mMoreIcon.setImageTintList(ColorStateList.valueOf(mIconTint));
-        }
         applyNotificationIconsTint();
     }
 
@@ -144,24 +145,54 @@
      * Updates the notifications with the given list of notifications to display.
      */
     public void updateNotificationIcons(NotificationData notificationData) {
-        final LinearLayout.LayoutParams params = generateIconLayoutParams();
 
-        ArrayList<NotificationData.Entry> activeNotifications =
-                notificationData.getActiveNotifications();
-        final int size = activeNotifications.size();
-        ArrayList<StatusBarIconView> toShow = new ArrayList<>(size);
+        updateIconsForLayout(notificationData, entry -> entry.icon, mNotificationIcons);
+        updateIconsForLayout(notificationData, entry -> entry.expandedIcon, mShelfIcons);
+
+        applyNotificationIconsTint();
+        ArrayList<NotificationData.Entry> activeNotifications
+                = notificationData.getActiveNotifications();
+        for (int i = 0; i < activeNotifications.size(); i++) {
+            NotificationData.Entry entry = activeNotifications.get(i);
+            boolean isPreL = Boolean.TRUE.equals(entry.expandedIcon.getTag(R.id.icon_is_pre_L));
+            boolean colorize = !isPreL
+                    || NotificationUtils.isGrayscale(entry.expandedIcon, mNotificationColorUtil);
+            if (colorize) {
+                int color = entry.getContrastedColor(mContext);
+                entry.expandedIcon.setImageTintList(ColorStateList.valueOf(color));
+            }
+        }
+    }
+
+    /**
+     * Updates the notification icons for a host layout. This will ensure that the notification
+     * host layout will have the same icons like the ones in here.
+     *
+     * @param notificationData the notification data to look up which notifications are relevant
+     * @param function A function to look up an icon view based on an entry
+     * @param hostLayout which layout should be updated
+     */
+    private void updateIconsForLayout(NotificationData notificationData,
+            Function<NotificationData.Entry, StatusBarIconView> function,
+            NotificationIconContainer hostLayout) {
+        ArrayList<StatusBarIconView> toShow = new ArrayList<>(
+                mNotificationScrollLayout.getChildCount());
 
         // Filter out ambient notifications and notification children.
-        for (int i = 0; i < size; i++) {
-            NotificationData.Entry ent = activeNotifications.get(i);
-            if (shouldShowNotification(ent, notificationData)) {
-                toShow.add(ent.icon);
+        for (int i = 0; i < mNotificationScrollLayout.getChildCount(); i++) {
+            View view = mNotificationScrollLayout.getChildAt(i);
+            if (view instanceof ExpandableNotificationRow) {
+                NotificationData.Entry ent = ((ExpandableNotificationRow) view).getEntry();
+                if (shouldShowNotification(ent, notificationData)) {
+                    toShow.add(function.apply(ent));
+                }
             }
         }
 
+
         ArrayList<View> toRemove = new ArrayList<>();
-        for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
-            View child = mNotificationIcons.getChildAt(i);
+        for (int i = 0; i < hostLayout.getChildCount(); i++) {
+            View child = hostLayout.getChildAt(i);
             if (!toShow.contains(child)) {
                 toRemove.add(child);
             }
@@ -169,29 +200,32 @@
 
         final int toRemoveCount = toRemove.size();
         for (int i = 0; i < toRemoveCount; i++) {
-            mNotificationIcons.removeView(toRemove.get(i));
+            hostLayout.removeView(toRemove.get(i));
         }
 
+        final LinearLayout.LayoutParams params = generateIconLayoutParams();
         for (int i = 0; i < toShow.size(); i++) {
             View v = toShow.get(i);
+            // The view might still be transiently added if it was just removed and added again
+            hostLayout.removeTransientView(v);
             if (v.getParent() == null) {
-                mNotificationIcons.addView(v, i, params);
+                hostLayout.addView(v, i, params);
             }
         }
 
+        hostLayout.setChangingViewPositions(true);
         // Re-sort notification icons
-        final int childCount = mNotificationIcons.getChildCount();
+        final int childCount = hostLayout.getChildCount();
         for (int i = 0; i < childCount; i++) {
-            View actual = mNotificationIcons.getChildAt(i);
+            View actual = hostLayout.getChildAt(i);
             StatusBarIconView expected = toShow.get(i);
             if (actual == expected) {
                 continue;
             }
-            mNotificationIcons.removeView(expected);
-            mNotificationIcons.addView(expected, i);
+            hostLayout.removeView(expected);
+            hostLayout.addView(expected, i);
         }
-
-        applyNotificationIconsTint();
+        hostLayout.setChangingViewPositions(false);
     }
 
     /**
@@ -206,6 +240,7 @@
                 v.setImageTintList(ColorStateList.valueOf(
                         StatusBarIconController.getTint(mTintArea, v, mIconTint)));
             }
+            v.setIconTint(mIconTint);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
new file mode 100644
index 0000000..2895890
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.phone;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
+import com.android.systemui.statusbar.StatusBarIconView;
+import com.android.systemui.statusbar.stack.AnimationFilter;
+import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.statusbar.stack.ViewState;
+
+import java.util.WeakHashMap;
+
+/**
+ * A container for notification icons. It handles overflowing icons properly and positions them
+ * correctly on the screen.
+ */
+public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
+    private static final String TAG = "NotificationIconContainer";
+    private static final boolean DEBUG = false;
+    private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
+        private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
+
+        @Override
+        public AnimationFilter getAnimationFilter() {
+            return mAnimationFilter;
+        }
+    }.setDuration(200);
+    
+    private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
+        private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
+
+        @Override
+        public AnimationFilter getAnimationFilter() {
+            return mAnimationFilter;
+        }
+    }.setDuration(200).setDelay(50);
+
+    private boolean mShowAllIcons = true;
+    private WeakHashMap<View, IconState> mIconStates = new WeakHashMap<>();
+    private int mDotPadding;
+    private int mStaticDotRadius;
+    private int mActualLayoutWidth = -1;
+    private float mActualPaddingEnd = -1;
+    private float mActualPaddingStart = -1;
+    private boolean mChangingViewPositions;
+    private int mAnimationStartIndex = -1;
+
+    public NotificationIconContainer(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initDimens();
+        setWillNotDraw(!DEBUG);
+    }
+
+    private void initDimens() {
+        mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
+        mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        Paint paint = new Paint();
+        paint.setColor(Color.RED);
+        paint.setStyle(Paint.Style.STROKE);
+        canvas.drawRect(getActualPaddingStart(), 0, getLayoutEnd(), getHeight(), paint);
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        initDimens();
+    }
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        float centerY = getHeight() / 2.0f;
+        // we layout all our children on the left at the top
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            // We need to layout all children even the GONE ones, such that the heights are
+            // calculated correctly as they are used to calculate how many we can fit on the screen
+            int width = child.getMeasuredWidth();
+            int height = child.getMeasuredHeight();
+            int top = (int) (centerY - height / 2.0f);
+            child.layout(0, top, width, top + height);
+        }
+        if (mShowAllIcons) {
+            resetViewStates();
+            calculateIconTranslations();
+            applyIconStates();
+        }
+    }
+
+    public void applyIconStates() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View child = getChildAt(i);
+            ViewState childState = mIconStates.get(child);
+            if (childState != null) {
+                childState.applyToView(child);
+            }
+        }
+        mAnimationStartIndex = -1;
+    }
+
+    @Override
+    public void onViewAdded(View child) {
+        super.onViewAdded(child);
+        if (!mChangingViewPositions) {
+            mIconStates.put(child, new IconState());
+        }
+        int childIndex = indexOfChild(child);
+        if (childIndex < getChildCount() - 1
+            && mIconStates.get(getChildAt(childIndex + 1)).iconAppearAmount > 0.0f) {
+            if (mAnimationStartIndex < 0) {
+                mAnimationStartIndex = childIndex;
+            } else {
+                mAnimationStartIndex = Math.min(mAnimationStartIndex, childIndex);
+            }
+        }
+    }
+
+    @Override
+    public void onViewRemoved(View child) {
+        super.onViewRemoved(child);
+        if (child instanceof StatusBarIconView) {
+            final StatusBarIconView icon = (StatusBarIconView) child;
+            if (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
+                    && child.getVisibility() == VISIBLE) {
+                int animationStartIndex = findFirstViewIndexAfter(icon.getTranslationX());
+                if (mAnimationStartIndex < 0) {
+                    mAnimationStartIndex = animationStartIndex;
+                } else {
+                    mAnimationStartIndex = Math.min(mAnimationStartIndex, animationStartIndex);
+                }
+            }
+            if (!mChangingViewPositions) {
+                mIconStates.remove(child);
+                addTransientView(icon, 0);
+                icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, true /* animate */,
+                        () -> removeTransientView(icon));
+            }
+        }
+    }
+
+    /**
+     * Finds the first view with a translation bigger then a given value
+     */
+    private int findFirstViewIndexAfter(float translationX) {
+        for (int i = 0; i < getChildCount(); i++) {
+            View view = getChildAt(i);
+            if (view.getTranslationX() > translationX) {
+                return i;
+            }
+        }
+        return getChildCount();
+    }
+
+    public WeakHashMap<View, IconState> resetViewStates() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View view = getChildAt(i);
+            ViewState iconState = mIconStates.get(view);
+            iconState.initFrom(view);
+            iconState.alpha = 1.0f;
+        }
+        return mIconStates;
+    }
+
+    /**
+     * Calulate the horizontal translations for each notification based on how much the icons
+     * are inserted into the notification container.
+     * If this is not a whole number, the fraction means by how much the icon is appearing.
+     */
+    public void calculateIconTranslations() {
+        float translationX = getActualPaddingStart();
+        int overflowingIconIndex = -1;
+        int lastTwoIconWidth = 0;
+        int childCount = getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            View view = getChildAt(i);
+            IconState iconState = mIconStates.get(view);
+            iconState.xTranslation = translationX;
+            iconState.visibleState = StatusBarIconView.STATE_ICON;
+            translationX += iconState.iconAppearAmount * view.getWidth();
+            if (translationX > getLayoutEnd()) {
+                // we are overflowing it with this icon
+                overflowingIconIndex = i - 1;
+                lastTwoIconWidth = view.getWidth();
+                break;
+            }
+        }
+        if (overflowingIconIndex != -1) {
+            int numDots = 1;
+            View overflowIcon = getChildAt(overflowingIconIndex);
+            IconState overflowState = mIconStates.get(overflowIcon);
+            lastTwoIconWidth += overflowIcon.getWidth();
+            int dotWidth = mStaticDotRadius * 2 + mDotPadding;
+            int totalDotLength = mStaticDotRadius * 6 + 2 * mDotPadding;
+            translationX = (getLayoutEnd() - lastTwoIconWidth / 2 - totalDotLength / 2)
+                    - overflowIcon.getWidth() * 0.3f + mStaticDotRadius;
+            float overflowStart = getLayoutEnd() - lastTwoIconWidth;
+            float overlapAmount = (overflowState.xTranslation - overflowStart)
+                    / overflowIcon.getWidth();
+            translationX += overlapAmount * dotWidth;
+            for (int i = overflowingIconIndex; i < childCount; i++) {
+                View view = getChildAt(i);
+                IconState iconState = mIconStates.get(view);
+                iconState.xTranslation = translationX;
+                if (numDots <= 3) {
+                    iconState.visibleState = StatusBarIconView.STATE_DOT;
+                    translationX += numDots == 3 ? 3 * dotWidth : dotWidth;
+                } else {
+                    iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
+                }
+                numDots++;
+            }
+        }
+        if (isLayoutRtl()) {
+            for (int i = 0; i < childCount; i++) {
+                View view = getChildAt(i);
+                IconState iconState = mIconStates.get(view);
+                iconState.xTranslation = getWidth() - iconState.xTranslation - view.getWidth();
+            }
+        }
+    }
+
+    private float getLayoutEnd() {
+        return getActualWidth() - getActualPaddingEnd();
+    }
+
+    private float getActualPaddingEnd() {
+        if (mActualPaddingEnd < 0) {
+            return getPaddingEnd();
+        }
+        return mActualPaddingEnd;
+    }
+
+    private float getActualPaddingStart() {
+        if (mActualPaddingStart < 0) {
+            return getPaddingStart();
+        }
+        return mActualPaddingStart;
+    }
+
+    /**
+     * Sets whether the layout should always show all icons.
+     * If this is true, the icon positions will be updated on layout.
+     * If this if false, the layout is managed from the outside and layouting won't trigger a
+     * repositioning of the icons.
+     */
+    public void setShowAllIcons(boolean showAllIcons) {
+        mShowAllIcons = showAllIcons;
+    }
+
+    public void setActualLayoutWidth(int actualLayoutWidth) {
+        mActualLayoutWidth = actualLayoutWidth;
+        if (DEBUG) {
+            invalidate();
+        }
+    }
+
+    public void setActualPaddingEnd(float paddingEnd) {
+        mActualPaddingEnd = paddingEnd;
+        if (DEBUG) {
+            invalidate();
+        }
+    }
+
+    public void setActualPaddingStart(float paddingStart) {
+        mActualPaddingStart = paddingStart;
+        if (DEBUG) {
+            invalidate();
+        }
+    }
+
+    public int getActualWidth() {
+        if (mActualLayoutWidth < 0) {
+            return getWidth();
+        }
+        return mActualLayoutWidth;
+    }
+
+    public void setChangingViewPositions(boolean changingViewPositions) {
+        mChangingViewPositions = changingViewPositions;
+    }
+
+    public class IconState extends ViewState {
+        public float iconAppearAmount = 1.0f;
+        public int visibleState;
+        public boolean justAdded = true;
+
+        @Override
+        public void applyToView(View view) {
+            if (view instanceof StatusBarIconView) {
+                StatusBarIconView icon = (StatusBarIconView) view;
+                AnimationProperties animationProperties = DOT_ANIMATION_PROPERTIES;
+                if (justAdded) {
+                    super.applyToView(icon);
+                    icon.setAlpha(0.0f);
+                    icon.setVisibleState(StatusBarIconView.STATE_HIDDEN, false /* animate */);
+                    animationProperties = ADD_ICON_PROPERTIES;
+                }
+                boolean animate = visibleState != icon.getVisibleState() || justAdded;
+                if (!animate && mAnimationStartIndex >= 0
+                        && (icon.getVisibleState() != StatusBarIconView.STATE_HIDDEN
+                            || visibleState != StatusBarIconView.STATE_HIDDEN)) {
+                    int viewIndex = indexOfChild(view);
+                    animate = viewIndex >= mAnimationStartIndex;
+                }
+                icon.setVisibleState(visibleState);
+                if (animate) {
+                    animateTo(icon, animationProperties);
+                } else {
+                    super.applyToView(view);
+                }
+            }
+            justAdded = false;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 068631d..523528d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -202,11 +202,12 @@
     private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
         @Override
         public void run() {
-            mHeadsUpAnimatingAway = false;
+            setHeadsUpAnimatingAway(false);
             notifyBarPanelExpansionChanged();
         }
     };
     private NotificationGroupManager mGroupManager;
+    private boolean mOpening;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -404,10 +405,11 @@
         int notificationPadding = Math.max(1, getResources().getDimensionPixelSize(
                 R.dimen.notification_divider_height));
         final int overflowheight = getResources().getDimensionPixelSize(
-                R.dimen.notification_summary_height);
-        float bottomStackSize = mNotificationStackScroller.getKeyguardBottomStackSize();
+                R.dimen.notification_shelf_height);
+        float shelfSize = mNotificationStackScroller.getNotificationShelf().getIntrinsicHeight()
+                + notificationPadding;
         float availableSpace = mNotificationStackScroller.getHeight() - minPadding - overflowheight
-                - bottomStackSize;
+                - shelfSize;
         int count = 0;
         for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
             ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
@@ -429,6 +431,16 @@
             availableSpace -= child.getMinHeight() + notificationPadding;
             if (availableSpace >= 0 && count < maximum) {
                 count++;
+            } else if (availableSpace > -shelfSize) {
+                // if we are exactly the last view, then we can show us still!
+                for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) {
+                    if (mNotificationStackScroller.getChildAt(j)
+                            instanceof ExpandableNotificationRow) {
+                        return count;
+                    }
+                }
+                count++;
+                return count;
             } else {
                 return count;
             }
@@ -546,9 +558,7 @@
     protected void flingToHeight(float vel, boolean expand, float target,
             float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
         mHeadsUpTouchHelper.notifyFling(!expand);
-        setClosingWithAlphaFadeout(!expand
-                && mNotificationStackScroller.getFirstChildIntrinsicHeight() <= mMaxFadeoutHeight
-                && getFadeoutAlpha() == 1.0f);
+        setClosingWithAlphaFadeout(!expand && getFadeoutAlpha() == 1.0f);
         super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
     }
 
@@ -723,6 +733,11 @@
     }
 
     @Override
+    protected float getOpeningHeight() {
+        return mNotificationStackScroller.getMinExpansionHeight();
+    }
+
+    @Override
     public boolean onTouchEvent(MotionEvent event) {
         if (mBlockTouches || (mQs != null && mQs.isCustomizing())) {
             return false;
@@ -1415,7 +1430,7 @@
             if (mKeyguardShowing) {
 
                 // On Keyguard, interpolate the QS expansion linearly to the panel expansion
-                t = expandedHeight / getMaxPanelHeight();
+                t = expandedHeight / (getMaxPanelHeight());
             } else {
 
                 // In Shade, interpolate linearly such that QS is closed whenever panel height is
@@ -1475,9 +1490,7 @@
         // and expanding/collapsing the whole panel from/to quick settings.
         if (mNotificationStackScroller.getNotGoneChildCount() == 0
                 && mShadeEmpty) {
-            notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight()
-                    + mNotificationStackScroller.getBottomStackPeekSize()
-                    + mNotificationStackScroller.getBottomStackSlowDownHeight();
+            notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
         }
         int maxQsHeight = mQsMaxExpansionHeight;
 
@@ -1508,8 +1521,7 @@
 
     private float getFadeoutAlpha() {
         float alpha = (getNotificationsTopY() + mNotificationStackScroller.getFirstItemMinHeight())
-                / (mQsMinExpansionHeight + mNotificationStackScroller.getBottomStackPeekSize()
-                - mNotificationStackScroller.getBottomStackSlowDownHeight());
+                / mQsMinExpansionHeight;
         alpha = Math.max(0, Math.min(alpha, 1));
         alpha = (float) Math.pow(alpha, 0.75);
         return alpha;
@@ -1988,9 +2000,9 @@
     @Override
     protected float getCannedFlingDurationFactor() {
         if (mQsExpanded) {
-            return 0.7f;
+            return 0.9f;
         } else {
-            return 0.6f;
+            return 0.8f;
         }
     }
 
@@ -2180,16 +2192,22 @@
 
     @Override
     public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
+        mNotificationStackScroller.setInHeadsUpPinnedMode(inPinnedMode);
         if (inPinnedMode) {
             mHeadsUpExistenceChangedRunnable.run();
             updateNotificationTranslucency();
         } else {
-            mHeadsUpAnimatingAway = true;
+            setHeadsUpAnimatingAway(true);
             mNotificationStackScroller.runAfterAnimationFinished(
                     mHeadsUpExistenceChangedRunnable);
         }
     }
 
+    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        mHeadsUpAnimatingAway = headsUpAnimatingAway;
+        mNotificationStackScroller.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+    }
+
     @Override
     public void onHeadsUpPinned(ExpandableNotificationRow headsUp) {
         mNotificationStackScroller.generateHeadsUpAnimation(headsUp, true);
@@ -2265,6 +2283,14 @@
     protected void updateExpandedHeight(float expandedHeight) {
         mNotificationStackScroller.setExpandedHeight(expandedHeight);
         updateKeyguardBottomAreaAlpha();
+        setOpening(expandedHeight <= getOpeningHeight());
+    }
+
+    private void setOpening(boolean opening) {
+        if (opening != mOpening) {
+            mOpening = opening;
+            mStatusBar.recomputeDisableFlags(false);
+        }
     }
 
     public void setPanelScrimMinFraction(float minFraction) {
@@ -2316,7 +2342,18 @@
     @Override
     public void setAlpha(float alpha) {
         super.setAlpha(alpha);
-        mNotificationStackScroller.setParentFadingOut(alpha != 1.0f);
+        updateFullyVisibleState();
+    }
+
+    @Override
+    public void setVisibility(int visibility) {
+        super.setVisibility(visibility);
+        updateFullyVisibleState();
+    }
+
+    private void updateFullyVisibleState() {
+        mNotificationStackScroller.setParentNotFullyVisible(getAlpha() != 1.0f
+                || getVisibility() != VISIBLE);
     }
 
     /**
@@ -2358,6 +2395,15 @@
         mGroupManager = groupManager;
     }
 
+    public boolean shouldHideNotificationIcons() {
+        return !mOpening && !isFullyCollapsed();
+    }
+
+    public boolean shouldAnimateIconHiding() {
+        // TODO: handle this correctly, not completely working yet
+        return mNotificationStackScroller.getTranslationX() != 0;
+    }
+
     private final FragmentListener mFragmentListener = new FragmentListener() {
         @Override
         public void onFragmentViewCreated(String tag, Fragment fragment) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index f2c57e5..87a3848 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -118,7 +118,7 @@
         boolean fullyOpened = false;
         if (SPEW) LOG("panelExpansionChanged: start state=%d", mState);
         PanelView pv = mPanel;
-        pv.setVisibility(expanded ? View.VISIBLE : View.INVISIBLE);
+        pv.setVisibility(expanded ? VISIBLE : INVISIBLE);
         // adjust any other panels that may be partially visible
         if (expanded) {
             if (mState == STATE_CLOSED) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 3de03b5..570d5d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.os.SystemClock;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.InputDevice;
@@ -50,6 +51,10 @@
 public abstract class PanelView extends FrameLayout {
     public static final boolean DEBUG = PanelBar.DEBUG;
     public static final String TAG = PanelView.class.getSimpleName();
+    private static final int INITIAL_OPENING_PEEK_DURATION = 200;
+    private static final int PEEK_ANIMATION_DURATION = 360;
+    private long mDownTime;
+    private float mMinExpandHeight;
 
     private final void logf(String fmt, Object... args) {
         Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
@@ -88,6 +93,7 @@
     private ObjectAnimator mPeekAnimator;
     private VelocityTrackerInterface mVelocityTracker;
     private FlingAnimationUtils mFlingAnimationUtils;
+    private FlingAnimationUtils mFlingAnimationUtilsClosing;
     private FalsingManager mFalsingManager;
 
     /**
@@ -106,9 +112,6 @@
     private Interpolator mBounceInterpolator;
     protected KeyguardBottomAreaView mKeyguardBottomArea;
 
-    private boolean mPeekPending;
-    private boolean mCollapseAfterPeek;
-
     /**
      * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
      */
@@ -118,13 +121,6 @@
     private boolean mGestureWaitForTouchSlop;
     private boolean mIgnoreXTouchSlop;
     private boolean mExpandLatencyTracking;
-    private Runnable mPeekRunnable = new Runnable() {
-        @Override
-        public void run() {
-            mPeekPending = false;
-            runPeekAnimation();
-        }
-    };
 
     protected void onExpandingFinished() {
         mBar.onExpandingFinished();
@@ -148,21 +144,17 @@
         }
     }
 
-    private void schedulePeek() {
-        mPeekPending = true;
-        long timeout = ViewConfiguration.getTapTimeout();
-        postOnAnimationDelayed(mPeekRunnable, timeout);
-        notifyBarPanelExpansionChanged();
-    }
-
-    private void runPeekAnimation() {
-        mPeekHeight = getPeekHeight();
+    private void runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished) {
+        mPeekHeight = peekHeight;
         if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
         if (mHeightAnimator != null) {
             return;
         }
+        if (mPeekAnimator != null) {
+            mPeekAnimator.cancel();
+        }
         mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight)
-                .setDuration(250);
+                .setDuration(duration);
         mPeekAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
         mPeekAnimator.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
@@ -175,10 +167,10 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 mPeekAnimator = null;
-                if (mCollapseAfterPeek && !mCancelled) {
+                if (!mCancelled && collapseWhenFinished) {
                     postOnAnimation(mPostCollapseRunnable);
                 }
-                mCollapseAfterPeek = false;
+
             }
         });
         notifyExpandingStarted();
@@ -189,6 +181,7 @@
     public PanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mFlingAnimationUtils = new FlingAnimationUtils(context, 0.6f);
+        mFlingAnimationUtilsClosing = new FlingAnimationUtils(context, 0.4f);
         mBounceInterpolator = new BounceInterpolator();
         mFalsingManager = FalsingManager.getInstance(context);
     }
@@ -267,11 +260,13 @@
             case MotionEvent.ACTION_DOWN:
                 startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
                 mJustPeeked = false;
+                mMinExpandHeight = 0.0f;
                 mPanelClosedOnDown = isFullyCollapsed();
                 mHasLayoutedSinceDown = false;
                 mUpdateFlingOnLayout = false;
                 mMotionAborted = false;
                 mPeekTouching = mPanelClosedOnDown;
+                mDownTime = SystemClock.uptimeMillis();
                 mTouchAboveFalsingThreshold = false;
                 mCollapsedAndHeadsUpOnDown = isFullyCollapsed()
                         && mHeadsUpManager.hasPinnedHeadsUp();
@@ -279,16 +274,16 @@
                     initVelocityTracker();
                 }
                 trackMovement(event);
-                if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning) ||
-                        mPeekPending || mPeekAnimator != null) {
+                if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning)
+                        || mPeekAnimator != null) {
                     cancelHeightAnimator();
                     cancelPeek();
                     mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning)
-                            || mPeekPending || mPeekAnimator != null;
+                            || mPeekAnimator != null;
                     onTrackingStarted();
                 }
                 if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()) {
-                    schedulePeek();
+                    startOpening();
                 }
                 break;
 
@@ -317,7 +312,7 @@
                 // y-component of the gesture, as we have no conflicting horizontal gesture.
                 if (Math.abs(h) > mTouchSlop
                         && (Math.abs(h) > Math.abs(x - mInitialTouchX)
-                                || mIgnoreXTouchSlop)) {
+                        || mIgnoreXTouchSlop)) {
                     mTouchSlopExceeded = true;
                     if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
                         if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
@@ -325,23 +320,30 @@
                             h = 0;
                         }
                         cancelHeightAnimator();
-                        removeCallbacks(mPeekRunnable);
-                        mPeekPending = false;
                         onTrackingStarted();
                     }
                 }
-                final float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
+                float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
                 if (newHeight > mPeekHeight) {
                     if (mPeekAnimator != null) {
                         mPeekAnimator.cancel();
                     }
                     mJustPeeked = false;
+                } else if (mPeekAnimator == null && mJustPeeked) {
+                    // The initial peek has finished, but we haven't dragged as far yet, lets
+                    // speed it up by starting at the peek height.
+                    mInitialOffsetOnTouch = mExpandedHeight;
+                    mInitialTouchY = y;
+                    mMinExpandHeight = mExpandedHeight;
+                    mJustPeeked = false;
                 }
+                newHeight = Math.max(newHeight, mMinExpandHeight);
                 if (-h >= getFalsingThreshold()) {
                     mTouchAboveFalsingThreshold = true;
                     mUpwardsWhenTresholdReached = isDirectionUpwards(x, y);
                 }
-                if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) && !isTrackingBlocked()) {
+                if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) &&
+                        !isTrackingBlocked()) {
                     setExpandedHeightInternal(newHeight);
                 }
 
@@ -357,6 +359,14 @@
         return !mGestureWaitForTouchSlop || mTracking;
     }
 
+    private void startOpening() {;
+        runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(),
+                false /* collapseWhenFinished */);
+        notifyBarPanelExpansionChanged();
+    }
+
+    protected abstract float getOpeningHeight();
+
     /**
      * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
      * horizontal direction
@@ -418,6 +428,15 @@
             if (mUpdateFlingOnLayout) {
                 mUpdateFlingVelocity = vel;
             }
+        } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking) {
+            long timePassed = SystemClock.uptimeMillis() - mDownTime;
+            if (timePassed < ViewConfiguration.getLongPressTimeout()) {
+                // Lets show the user that he can actually expand the panel
+                runPeekAnimation(PEEK_ANIMATION_DURATION, getPeekHeight(), true /* collapseWhenFinished */);
+            } else {
+                // We need to collapse the panel since we peeked to the small height.
+                postOnAnimation(mPostCollapseRunnable);
+            }
         } else {
             boolean expands = onEmptySpaceClick(mInitialTouchX);
             onTrackingStopped(expands);
@@ -448,7 +467,6 @@
     protected void onTrackingStarted() {
         endClosing();
         mTracking = true;
-        mCollapseAfterPeek = false;
         mBar.onTrackingStarted();
         notifyExpandingStarted();
         notifyBarPanelExpansionChanged();
@@ -482,7 +500,10 @@
             case MotionEvent.ACTION_DOWN:
                 mStatusBar.userActivity();
                 mAnimatingOnDown = mHeightAnimator != null;
-                if (mAnimatingOnDown && mClosing && !mHintAnimationRunning || mPeekPending || mPeekAnimator != null) {
+                mMinExpandHeight = 0.0f;
+                mDownTime = SystemClock.uptimeMillis();
+                if (mAnimatingOnDown && mClosing && !mHintAnimationRunning
+                        || mPeekAnimator != null) {
                     cancelHeightAnimator();
                     cancelPeek();
                     mTouchSlopExceeded = true;
@@ -639,7 +660,7 @@
     protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
             boolean expandBecauseOfFalsing) {
         cancelPeek();
-        float target = expand ? getMaxPanelHeight() : 0.0f;
+        float target = expand ? getMaxPanelHeight() : 0;
         if (!expand) {
             mClosing = true;
         }
@@ -672,8 +693,7 @@
                 animator.setDuration(350);
             }
         } else {
-            mFlingAnimationUtils.applyDismissing(animator, mExpandedHeight, target, vel,
-                    getHeight());
+            mFlingAnimationUtilsClosing.apply(animator, mExpandedHeight, target, vel, getHeight());
 
             // Make it shorter if we run a canned animation
             if (vel == 0) {
@@ -742,7 +762,6 @@
                 && mHeightAnimator == null
                 && !isFullyCollapsed()
                 && currentMaxPanelHeight != mExpandedHeight
-                && !mPeekPending
                 && mPeekAnimator == null
                 && !mPeekTouching) {
             setExpandedHeight(currentMaxPanelHeight);
@@ -769,10 +788,8 @@
             }
         }
 
-        mExpandedHeight = Math.max(0, mExpandedHeight);
-        mExpandedFraction = Math.min(1f, fhWithoutOverExpansion == 0
-                ? 0
-                : mExpandedHeight / fhWithoutOverExpansion);
+        mExpandedFraction = Math.min(1f,
+                fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion);
         onHeightUpdated(mExpandedHeight);
         notifyBarPanelExpansionChanged();
     }
@@ -816,7 +833,7 @@
     }
 
     public boolean isFullyCollapsed() {
-        return mExpandedHeight <= 0;
+        return mExpandedFraction <= 0.0f;
     }
 
     public boolean isCollapsing() {
@@ -833,16 +850,7 @@
 
     public void collapse(boolean delayed, float speedUpFactor) {
         if (DEBUG) logf("collapse: " + this);
-        if (mPeekPending || mPeekAnimator != null) {
-            mCollapseAfterPeek = true;
-            if (mPeekPending) {
-
-                // We know that the whole gesture is just a peek triggered by a simple click, so
-                // better start it now.
-                removeCallbacks(mPeekRunnable);
-                mPeekRunnable.run();
-            }
-        } else if (!isFullyCollapsed() && !mTracking && !mClosing) {
+        if (!isFullyCollapsed() && !mTracking && !mClosing) {
             cancelHeightAnimator();
             notifyExpandingStarted();
 
@@ -866,13 +874,11 @@
     };
 
     public void cancelPeek() {
-        boolean cancelled = mPeekPending;
+        boolean cancelled = false;
         if (mPeekAnimator != null) {
             cancelled = true;
             mPeekAnimator.cancel();
         }
-        removeCallbacks(mPeekRunnable);
-        mPeekPending = false;
 
         if (cancelled) {
             // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also
@@ -1048,7 +1054,7 @@
     }
 
     protected void notifyBarPanelExpansionChanged() {
-        mBar.panelExpansionChanged(mExpandedFraction, mExpandedFraction > 0f || mPeekPending
+        mBar.panelExpansionChanged(mExpandedFraction, mExpandedFraction > 0f
                 || mPeekAnimator != null || mInstantExpanding || isPanelVisibleBecauseOfHeadsUp()
                 || mTracking || mHeightAnimator != null);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 6f13ba5..bb86d6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -164,7 +164,7 @@
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationData.Entry;
-import com.android.systemui.statusbar.NotificationOverflowContainer;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.SignalClusterView;
@@ -197,7 +197,7 @@
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout
         .OnChildLocationsChangedListener;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
-import com.android.systemui.statusbar.stack.StackViewState;
+import com.android.systemui.statusbar.stack.ExpandableViewState;
 import com.android.systemui.volume.VolumeComponent;
 
 import java.io.FileDescriptor;
@@ -569,8 +569,8 @@
      */
     protected boolean mStartedGoingToSleep;
 
-    private static final int VISIBLE_LOCATIONS = StackViewState.LOCATION_FIRST_HUN
-            | StackViewState.LOCATION_MAIN_AREA;
+    private static final int VISIBLE_LOCATIONS = ExpandableViewState.LOCATION_FIRST_HUN
+            | ExpandableViewState.LOCATION_MAIN_AREA;
 
     private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
             new OnChildLocationsChangedListener() {
@@ -659,10 +659,12 @@
         array.clear();
     }
 
-    private final View.OnClickListener mOverflowClickListener = new View.OnClickListener() {
+    private final View.OnClickListener mShelfClickListener = new View.OnClickListener() {
         @Override
         public void onClick(View v) {
-            goToLockedShade(null);
+            if (mState == StatusBarState.KEYGUARD) {
+                goToLockedShade(null);
+            }
         }
     };
     private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
@@ -812,7 +814,7 @@
         mStackScroller.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setOnGroupChangeListener(mStackScroller);
 
-        inflateOverflowContainer();
+        inflateShelf();
         inflateEmptyShadeView();
         inflateDismissView();
         mExpandedContents = mStackScroller;
@@ -1049,13 +1051,13 @@
         return new BatteryControllerImpl(mContext);
     }
 
-    private void inflateOverflowContainer() {
-        mKeyguardIconOverflowContainer =
-                (NotificationOverflowContainer) LayoutInflater.from(mContext).inflate(
-                        R.layout.status_bar_notification_keyguard_overflow, mStackScroller, false);
-        mKeyguardIconOverflowContainer.setOnActivatedListener(this);
-        mKeyguardIconOverflowContainer.setOnClickListener(mOverflowClickListener);
-        mStackScroller.setOverflowContainer(mKeyguardIconOverflowContainer);
+    private void inflateShelf() {
+        mNotificationShelf =
+                (NotificationShelf) LayoutInflater.from(mContext).inflate(
+                        R.layout.status_bar_notification_shelf, mStackScroller, false);
+        mNotificationShelf.setOnActivatedListener(this);
+        mNotificationShelf.setOnClickListener(mShelfClickListener);
+        mStackScroller.setShelf(mNotificationShelf);
     }
 
     @Override
@@ -1072,7 +1074,7 @@
         updateClearAll();
         inflateEmptyShadeView();
         updateEmptyShadeView();
-        inflateOverflowContainer();
+        inflateShelf();
         mStatusBarKeyguardViewManager.onDensityOrFontScaleChanged();
         mUserInfoController.onDensityOrFontScaleChanged();
         if (mUserSwitcherController != null) {
@@ -1866,7 +1868,7 @@
         mTmpChildOrderMap.clear();
 
         updateRowStates();
-        updateSpeedbump();
+        updateSpeedBumpIndex();
         updateClearAll();
         updateEmptyShadeView();
 
@@ -1987,8 +1989,8 @@
         mNotificationPanel.setShadeEmpty(showEmptyShade);
     }
 
-    private void updateSpeedbump() {
-        int speedbumpIndex = -1;
+    private void updateSpeedBumpIndex() {
+        int speedBumpIndex = -1;
         int currentIndex = 0;
         final int N = mStackScroller.getChildCount();
         for (int i = 0; i < N; i++) {
@@ -1998,12 +2000,17 @@
             }
             ExpandableNotificationRow row = (ExpandableNotificationRow) view;
             if (mNotificationData.isAmbient(row.getStatusBarNotification().getKey())) {
-                speedbumpIndex = currentIndex;
+                speedBumpIndex = currentIndex;
                 break;
             }
             currentIndex++;
         }
-        mStackScroller.updateSpeedBumpIndex(speedbumpIndex);
+        boolean noAmbient = false;
+        if (speedBumpIndex == -1) {
+            speedBumpIndex = currentIndex;
+            noAmbient = true;
+        }
+        mStackScroller.updateSpeedBumpIndex(speedBumpIndex, noAmbient);
     }
 
     public static boolean isTopLevelChild(Entry entry) {
@@ -2375,8 +2382,7 @@
     }
 
     protected int adjustDisableFlags(int state) {
-        if (!mLaunchTransitionFadingAway && !mKeyguardFadingAway
-                && (mExpandedVisible || mBouncerShowing || mWaitingForKeyguardExit)) {
+        if (!mLaunchTransitionFadingAway && !mKeyguardFadingAway && shouldHideNotificationIcons()) {
             state |= StatusBarManager.DISABLE_NOTIFICATION_ICONS;
             state |= StatusBarManager.DISABLE_SYSTEM_INFO;
         }
@@ -2391,6 +2397,10 @@
         return state;
     }
 
+    private boolean shouldHideNotificationIcons() {
+        return mExpandedVisible && mNotificationPanel.shouldHideNotificationIcons();
+    }
+
     /**
      * State is one or more of the DISABLE constants from StatusBarManager.
      */
@@ -2497,7 +2507,7 @@
      *
      * This needs to be called if state used by {@link #adjustDisableFlags} changes.
      */
-    private void recomputeDisableFlags(boolean animate) {
+    public void recomputeDisableFlags(boolean animate) {
         disable(mDisabledUnmodified1, mDisabledUnmodified2, animate);
     }
 
@@ -2694,6 +2704,14 @@
         mFalsingManager.onScreenOff();
     }
 
+    public NotificationShelf getNotificationShelf() {
+        return mNotificationShelf;
+    }
+
+    public NotificationStackScrollLayout getNotificationScrollLayout() {
+        return mStackScroller;
+    }
+
     public boolean isPulsing() {
         return mDozeScrimController.isPulsing();
     }
@@ -2935,7 +2953,7 @@
         runPostCollapseRunnables();
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
         showBouncer();
-        recomputeDisableFlags(true /* animate */);
+        recomputeDisableFlags(shouldAnimatIconHiding() /* animate */);
 
         // Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
         // the bouncer appear animation.
@@ -2944,6 +2962,10 @@
         }
     }
 
+    private boolean shouldAnimatIconHiding() {
+        return mNotificationPanel.shouldAnimateIconHiding();
+    }
+
     public boolean interceptTouchEvent(MotionEvent event) {
         if (DEBUG_GESTURES) {
             if (event.getActionMasked() != MotionEvent.ACTION_MOVE) {
@@ -4153,7 +4175,7 @@
                 mScrimController.forceHideScrims(true /* hide */);
                 updateMediaMetaData(false, true);
                 mNotificationPanel.setAlpha(1);
-                mStackScroller.setParentFadingOut(true);
+                mStackScroller.setParentNotFullyVisible(true);
                 mNotificationPanel.animate()
                         .alpha(0)
                         .setStartDelay(FADE_KEYGUARD_START_DELAY)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index a4b76ebb..249022e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -21,7 +21,6 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.content.Context;
-import android.graphics.Color;
 import android.graphics.Rect;
 import android.support.v4.graphics.ColorUtils;
 import android.view.View;
@@ -37,7 +36,7 @@
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.stack.StackStateAnimator;
+import com.android.systemui.statusbar.stack.ViewState;
 
 /**
  * Controls both the scrim behind the notifications and in front of the notifications (when a
@@ -478,14 +477,14 @@
             return;
         }
 
-        ValueAnimator previousAnimator = StackStateAnimator.getChildTag(scrim,
+        ValueAnimator previousAnimator = ViewState.getChildTag(scrim,
                 TAG_KEY_ANIM);
         float animEndValue = -1;
         if (previousAnimator != null) {
             if (animate || alpha == currentAlpha) {
                 previousAnimator.cancel();
             } else {
-                animEndValue = StackStateAnimator.getChildTag(scrim, TAG_END_ALPHA);
+                animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA);
             }
         }
         if (alpha != currentAlpha && alpha != animEndValue) {
@@ -495,10 +494,8 @@
                 scrim.setTag(TAG_END_ALPHA, alpha);
             } else {
                 if (previousAnimator != null) {
-                    float previousStartValue = StackStateAnimator.getChildTag(scrim,
-                            TAG_START_ALPHA);
-                    float previousEndValue = StackStateAnimator.getChildTag(scrim,
-                            TAG_END_ALPHA);
+                    float previousStartValue = ViewState.getChildTag(scrim, TAG_START_ALPHA);
+                    float previousEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA);
                     // we need to increase all animation keyframes of the previous animator by the
                     // relative change to the end value
                     PropertyValuesHolder[] values = previousAnimator.getValues();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index dbe7f96..a948a08 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -44,6 +44,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SystemUIFactory;
 import com.android.systemui.statusbar.NotificationData;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.tuner.TunerService;
@@ -74,6 +75,7 @@
 
     private NotificationIconAreaController mNotificationIconAreaController;
     private View mNotificationIconAreaInner;
+    private NotificationShelf mNotificationShelf;
 
     private BatteryMeterView mBatteryMeterView;
     private BatteryMeterView mBatteryMeterViewKeyguard;
@@ -123,6 +125,7 @@
         mStatusIcons = (LinearLayout) statusBar.findViewById(R.id.statusIcons);
         mSignalCluster = (SignalClusterView) statusBar.findViewById(R.id.signal_cluster);
 
+        mNotificationShelf = phoneStatusBar.getNotificationShelf();
         mNotificationIconAreaController = SystemUIFactory.getInstance()
                 .createNotificationIconAreaController(context, phoneStatusBar);
         mNotificationIconAreaInner =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
index 995f4dd..29c0705 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowManager.java
@@ -137,7 +137,8 @@
 
     private void applyFocusableFlag(State state) {
         boolean panelFocusable = state.statusBarFocusable && state.panelExpanded;
-        if (state.bouncerShowing || BaseStatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
+        if (state.bouncerShowing && (state.keyguardOccluded || state.keyguardNeedsInput)
+                || BaseStatusBar.ENABLE_REMOTE_INPUT && state.remoteInputActive) {
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
             mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
         } else if (state.isKeyguardShowingAndNotOccluded() || panelFocusable) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 6217433..f6dd88d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -235,7 +235,7 @@
     @Override
     public boolean dispatchTouchEvent(MotionEvent ev) {
         if (ev.getActionMasked() == MotionEvent.ACTION_DOWN
-                && mNotificationPanel.getExpandedHeight() == 0f) {
+                && mNotificationPanel.isFullyCollapsed()) {
             mNotificationPanel.startExpandLatencyTracking();
         }
         mFalsingManager.onTouchEvent(ev, getWidth(), getHeight());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index f6c0942..c21c493 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -103,6 +103,7 @@
     private boolean mWaitingOnCollapseWhenGoingAway;
     private boolean mIsObserving;
     private boolean mRemoteInputActive;
+    private float mExpandedHeight;
 
     public HeadsUpManager(final Context context, View statusBarWindowView,
                           NotificationGroupManager groupManager) {
@@ -513,7 +514,7 @@
                 row = groupSummary;
             }
         }
-        return row.getPinnedHeadsUpHeight(true /* atLeastMinHeight */);
+        return row.getPinnedHeadsUpHeight();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index 81da672..26f74ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -16,9 +16,12 @@
 
 package com.android.systemui.statusbar.stack;
 
+import android.content.Context;
 import android.view.View;
 
+import com.android.systemui.R;
 import com.android.systemui.statusbar.ActivatableNotificationView;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import java.util.ArrayList;
@@ -44,6 +47,38 @@
     private float mMaxHeadsUpTranslation;
     private boolean mDismissAllInProgress;
     private int mLayoutMinHeight;
+    private NotificationShelf mShelf;
+    private int mZDistanceBetweenElements;
+    private int mBaseZHeight;
+    private int mMaxLayoutHeight;
+    private ActivatableNotificationView mLastVisibleBackgroundChild;
+
+    public AmbientState(Context context) {
+        reload(context);
+    }
+
+    /**
+     * Reload the dimens e.g. if the density changed.
+     */
+    public void reload(Context context) {
+        mZDistanceBetweenElements = Math.max(1, context.getResources()
+                .getDimensionPixelSize(R.dimen.z_distance_between_notifications));
+        mBaseZHeight = 4 * mZDistanceBetweenElements;
+    }
+
+    /**
+     * @return the basic Z height on which notifications remain.
+     */
+    public int getBaseZHeight() {
+        return mBaseZHeight;
+    }
+
+    /**
+     * @return the distance in Z between two overlaying notifications.
+     */
+    public int getZDistanceBetweenElements() {
+        return mZDistanceBetweenElements;
+    }
 
     public int getScrollY() {
         return mScrollY;
@@ -122,8 +157,8 @@
         return mSpeedBumpIndex;
     }
 
-    public void setSpeedBumpIndex(int speedBumpIndex) {
-        mSpeedBumpIndex = speedBumpIndex;
+    public void setSpeedBumpIndex(int shelfIndex) {
+        mSpeedBumpIndex = shelfIndex;
     }
 
     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
@@ -151,7 +186,7 @@
     }
 
     public int getInnerHeight() {
-        return Math.max(mLayoutHeight - mTopPadding, mLayoutMinHeight);
+        return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding, mLayoutMinHeight);
     }
 
     public boolean isShadeExpanded() {
@@ -181,4 +216,29 @@
     public void setLayoutMinHeight(int layoutMinHeight) {
         mLayoutMinHeight = layoutMinHeight;
     }
+
+    public void setShelf(NotificationShelf shelf) {
+        mShelf = shelf;
+    }
+
+    public NotificationShelf getShelf() {
+        return mShelf;
+    }
+
+    public void setLayoutMaxHeight(int maxLayoutHeight) {
+        mMaxLayoutHeight = maxLayoutHeight;
+    }
+
+    /**
+     * Sets the last visible view of the host layout, that has a background, i.e the very last
+     * view in the shade, without the clear all button.
+     */
+    public void setLastVisibleBackgroundChild(
+            ActivatableNotificationView lastVisibleBackgroundChild) {
+        mLastVisibleBackgroundChild = lastVisibleBackgroundChild;
+    }
+
+    public ActivatableNotificationView getLastVisibleBackgroundChild() {
+        return mLastVisibleBackgroundChild;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
index 561b18a..d3d58f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationFilter.java
@@ -23,6 +23,7 @@
  */
 public class AnimationFilter {
     boolean animateAlpha;
+    boolean animateX;
     boolean animateY;
     boolean animateZ;
     boolean animateHeight;
@@ -42,6 +43,11 @@
         return this;
     }
 
+    public AnimationFilter animateX() {
+        animateX = true;
+        return this;
+    }
+
     public AnimationFilter animateY() {
         animateY = true;
         return this;
@@ -116,6 +122,7 @@
 
     private void combineFilter(AnimationFilter filter) {
         animateAlpha |= filter.animateAlpha;
+        animateX |= filter.animateX;
         animateY |= filter.animateY;
         animateZ |= filter.animateZ;
         animateHeight |= filter.animateHeight;
@@ -129,6 +136,7 @@
 
     private void reset() {
         animateAlpha = false;
+        animateX = false;
         animateY = false;
         animateZ = false;
         animateHeight = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java
new file mode 100644
index 0000000..0de774d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AnimationProperties.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.stack;
+
+import android.animation.AnimatorListenerAdapter;
+import android.util.Property;
+import android.view.View;
+import android.view.animation.Interpolator;
+
+/**
+ * Properties for a View animation
+ */
+public class AnimationProperties {
+    public long duration;
+    public long delay;
+
+    /**
+     * @return an animation filter for this animation.
+     */
+    public AnimationFilter getAnimationFilter() {
+        return new AnimationFilter();
+    }
+
+    /**
+     * @return a listener that should be run whenever any property finished its animation
+     */
+    public AnimatorListenerAdapter getAnimationFinishListener() {
+        return null;
+    }
+
+    public boolean wasAdded(View view) {
+        return false;
+    }
+
+    /**
+     * Get a custom interpolator for a property instead of the normal one.
+     */
+    public Interpolator getCustomInterpolator(View child, Property property) {
+        return null;
+    }
+
+    public AnimationProperties setDuration(long duration) {
+        this.duration = duration;
+        return this;
+    }
+
+    public AnimationProperties setDelay(long delay) {
+        this.delay = delay;
+        return this;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
new file mode 100644
index 0000000..58e6838
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) 2015 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.statusbar.stack;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
+import android.view.View;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableView;
+
+/**
+* A state of an expandable view
+*/
+public class ExpandableViewState extends ViewState {
+
+    private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
+    private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
+    private static final int TAG_ANIMATOR_SHADOW_ALPHA = R.id.shadow_alpha_animator_tag;
+    private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
+    private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
+    private static final int TAG_END_SHADOW_ALPHA = R.id.shadow_alpha_animator_end_value_tag;
+    private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
+    private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
+    private static final int TAG_START_SHADOW_ALPHA = R.id.shadow_alpha_animator_start_value_tag;
+
+    // These are flags such that we can create masks for filtering.
+
+    /**
+     * No known location. This is the default and should not be set after an invocation of the
+     * algorithm.
+     */
+    public static final int LOCATION_UNKNOWN = 0x00;
+
+    /**
+     * The location is the first heads up notification, so on the very top.
+     */
+    public static final int LOCATION_FIRST_HUN = 0x01;
+
+    /**
+     * The location is hidden / scrolled away on the top.
+     */
+    public static final int LOCATION_HIDDEN_TOP = 0x02;
+
+    /**
+     * The location is in the main area of the screen and visible.
+     */
+    public static final int LOCATION_MAIN_AREA = 0x04;
+
+    /**
+     * The location is in the bottom stack and it's peeking
+     */
+    public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x08;
+
+    /**
+     * The location is in the bottom stack and it's hidden.
+     */
+    public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10;
+
+    /**
+     * The view isn't laid out at all.
+     */
+    public static final int LOCATION_GONE = 0x40;
+
+    public int height;
+    public boolean dimmed;
+    public boolean dark;
+    public boolean hideSensitive;
+    public boolean belowSpeedBump;
+    public float shadowAlpha;
+    public boolean inShelf;
+
+    /**
+     * How much the child overlaps with the previous child on top. This is used to
+     * show the background properly when the child on top is translating away.
+     */
+    public int clipTopAmount;
+
+    /**
+     * The index of the view, only accounting for views not equal to GONE
+     */
+    public int notGoneIndex;
+
+    /**
+     * The location this view is currently rendered at.
+     *
+     * <p>See <code>LOCATION_</code> flags.</p>
+     */
+    public int location;
+
+    /**
+     * Whether a child in a group is being clipped at the bottom.
+     */
+    public boolean isBottomClipped;
+
+    @Override
+    public void copyFrom(ViewState viewState) {
+        super.copyFrom(viewState);
+        if (viewState instanceof ExpandableViewState) {
+            ExpandableViewState svs = (ExpandableViewState) viewState;
+            height = svs.height;
+            dimmed = svs.dimmed;
+            shadowAlpha = svs.shadowAlpha;
+            dark = svs.dark;
+            hideSensitive = svs.hideSensitive;
+            belowSpeedBump = svs.belowSpeedBump;
+            clipTopAmount = svs.clipTopAmount;
+            notGoneIndex = svs.notGoneIndex;
+            location = svs.location;
+            isBottomClipped = svs.isBottomClipped;
+        }
+    }
+
+    /**
+     * Applies a {@link ExpandableViewState} to a {@link ExpandableView}.
+     */
+    @Override
+    public void applyToView(View view) {
+        super.applyToView(view);
+        if (view instanceof ExpandableView) {
+            ExpandableView expandableView = (ExpandableView) view;
+
+            int height = expandableView.getActualHeight();
+            int newHeight = this.height;
+
+            // apply height
+            if (height != newHeight) {
+                expandableView.setActualHeight(newHeight, false /* notifyListeners */);
+            }
+
+            float shadowAlpha = expandableView.getShadowAlpha();
+            float newShadowAlpha = this.shadowAlpha;
+
+            // apply shadowAlpha
+            if (shadowAlpha != newShadowAlpha) {
+                expandableView.setShadowAlpha(newShadowAlpha);
+            }
+
+            // apply dimming
+            expandableView.setDimmed(this.dimmed, false /* animate */);
+
+            // apply hiding sensitive
+            expandableView.setHideSensitive(
+                    this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
+
+            // apply below shelf speed bump
+            expandableView.setBelowSpeedBump(this.belowSpeedBump);
+
+            // apply dark
+            expandableView.setDark(this.dark, false /* animate */, 0 /* delay */);
+
+            // apply clipping
+            float oldClipTopAmount = expandableView.getClipTopAmount();
+            if (oldClipTopAmount != this.clipTopAmount) {
+                expandableView.setClipTopAmount(this.clipTopAmount);
+            }
+
+            expandableView.setTransformingInShelf(false);
+            expandableView.setInShelf(inShelf);
+        }
+    }
+
+    @Override
+    public void animateTo(View child, AnimationProperties properties) {
+        super.animateTo(child, properties);
+        if (!(child instanceof ExpandableView)) {
+            return;
+        }
+        ExpandableView expandableView = (ExpandableView) child;
+        AnimationFilter animationFilter = properties.getAnimationFilter();
+
+        // start height animation
+        if (this.height != expandableView.getActualHeight()) {
+            startHeightAnimation(expandableView, properties);
+        }  else {
+            abortAnimation(child, TAG_ANIMATOR_HEIGHT);
+        }
+
+        // start shadow alpha animation
+        if (this.shadowAlpha != expandableView.getShadowAlpha()) {
+            startShadowAlphaAnimation(expandableView, properties);
+        } else {
+            abortAnimation(child, TAG_ANIMATOR_SHADOW_ALPHA);
+        }
+
+        // start top inset animation
+        if (this.clipTopAmount != expandableView.getClipTopAmount()) {
+            startInsetAnimation(expandableView, properties);
+        } else {
+            abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
+        }
+
+        // start dimmed animation
+        expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
+
+        // apply below the speed bump
+        expandableView.setBelowSpeedBump(this.belowSpeedBump);
+
+        // start hiding sensitive animation
+        expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive,
+                properties.delay, properties.duration);
+
+        // start dark animation
+        expandableView.setDark(this.dark, animationFilter.animateDark, properties.delay);
+
+        if (properties.wasAdded(child) && !hidden) {
+            expandableView.performAddAnimation(properties.delay, properties.duration);
+        }
+
+        if (!expandableView.isInShelf() && this.inShelf) {
+            expandableView.setTransformingInShelf(true);
+        }
+        expandableView.setInShelf(this.inShelf);
+    }
+
+    private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) {
+        Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
+        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
+        int newEndValue = this.height;
+        if (previousEndValue != null && previousEndValue == newEndValue) {
+            return;
+        }
+        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
+        AnimationFilter filter = properties.getAnimationFilter();
+        if (!filter.animateHeight) {
+            // just a local update was performed
+            if (previousAnimator != null) {
+                // we need to increase all animation keyframes of the previous animator by the
+                // relative change to the end value
+                PropertyValuesHolder[] values = previousAnimator.getValues();
+                int relativeDiff = newEndValue - previousEndValue;
+                int newStartValue = previousStartValue + relativeDiff;
+                values[0].setIntValues(newStartValue, newEndValue);
+                child.setTag(TAG_START_HEIGHT, newStartValue);
+                child.setTag(TAG_END_HEIGHT, newEndValue);
+                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+                return;
+            } else {
+                // no new animation needed, let's just apply the value
+                child.setActualHeight(newEndValue, false);
+                return;
+            }
+        }
+
+        ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                child.setActualHeight((int) animation.getAnimatedValue(),
+                        false /* notifyListeners */);
+            }
+        });
+        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
+        animator.setDuration(newDuration);
+        if (properties.delay > 0 && (previousAnimator == null
+                || previousAnimator.getAnimatedFraction() == 0)) {
+            animator.setStartDelay(properties.delay);
+        }
+        AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
+        if (listener != null) {
+            animator.addListener(listener);
+        }
+        // remove the tag when the animation is finished
+        animator.addListener(new AnimatorListenerAdapter() {
+            boolean mWasCancelled;
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                child.setTag(TAG_ANIMATOR_HEIGHT, null);
+                child.setTag(TAG_START_HEIGHT, null);
+                child.setTag(TAG_END_HEIGHT, null);
+                child.setActualHeightAnimating(false);
+                if (!mWasCancelled && child instanceof ExpandableNotificationRow) {
+                    ((ExpandableNotificationRow) child).setGroupExpansionChanging(
+                            false /* isExpansionChanging */);
+                }
+            }
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mWasCancelled = false;
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mWasCancelled = true;
+            }
+        });
+        startAnimator(animator, listener);
+        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
+        child.setTag(TAG_START_HEIGHT, child.getActualHeight());
+        child.setTag(TAG_END_HEIGHT, newEndValue);
+        child.setActualHeightAnimating(true);
+    }
+
+    private void startShadowAlphaAnimation(final ExpandableView child,
+            AnimationProperties properties) {
+        Float previousStartValue = getChildTag(child, TAG_START_SHADOW_ALPHA);
+        Float previousEndValue = getChildTag(child, TAG_END_SHADOW_ALPHA);
+        float newEndValue = this.shadowAlpha;
+        if (previousEndValue != null && previousEndValue == newEndValue) {
+            return;
+        }
+        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SHADOW_ALPHA);
+        AnimationFilter filter = properties.getAnimationFilter();
+        if (!filter.animateShadowAlpha) {
+            // just a local update was performed
+            if (previousAnimator != null) {
+                // we need to increase all animation keyframes of the previous animator by the
+                // relative change to the end value
+                PropertyValuesHolder[] values = previousAnimator.getValues();
+                float relativeDiff = newEndValue - previousEndValue;
+                float newStartValue = previousStartValue + relativeDiff;
+                values[0].setFloatValues(newStartValue, newEndValue);
+                child.setTag(TAG_START_SHADOW_ALPHA, newStartValue);
+                child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
+                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+                return;
+            } else {
+                // no new animation needed, let's just apply the value
+                child.setShadowAlpha(newEndValue);
+                return;
+            }
+        }
+
+        ValueAnimator animator = ValueAnimator.ofFloat(child.getShadowAlpha(), newEndValue);
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                child.setShadowAlpha((float) animation.getAnimatedValue());
+            }
+        });
+        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
+        animator.setDuration(newDuration);
+        if (properties.delay > 0 && (previousAnimator == null
+                || previousAnimator.getAnimatedFraction() == 0)) {
+            animator.setStartDelay(properties.delay);
+        }
+        AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
+        if (listener != null) {
+            animator.addListener(listener);
+        }
+        // remove the tag when the animation is finished
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, null);
+                child.setTag(TAG_START_SHADOW_ALPHA, null);
+                child.setTag(TAG_END_SHADOW_ALPHA, null);
+            }
+        });
+        startAnimator(animator, listener);
+        child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, animator);
+        child.setTag(TAG_START_SHADOW_ALPHA, child.getShadowAlpha());
+        child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
+    }
+
+    private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) {
+        Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
+        Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
+        int newEndValue = this.clipTopAmount;
+        if (previousEndValue != null && previousEndValue == newEndValue) {
+            return;
+        }
+        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
+        AnimationFilter filter = properties.getAnimationFilter();
+        if (!filter.animateTopInset) {
+            // just a local update was performed
+            if (previousAnimator != null) {
+                // we need to increase all animation keyframes of the previous animator by the
+                // relative change to the end value
+                PropertyValuesHolder[] values = previousAnimator.getValues();
+                int relativeDiff = newEndValue - previousEndValue;
+                int newStartValue = previousStartValue + relativeDiff;
+                values[0].setIntValues(newStartValue, newEndValue);
+                child.setTag(TAG_START_TOP_INSET, newStartValue);
+                child.setTag(TAG_END_TOP_INSET, newEndValue);
+                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+                return;
+            } else {
+                // no new animation needed, let's just apply the value
+                child.setClipTopAmount(newEndValue);
+                return;
+            }
+        }
+
+        ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
+        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                child.setClipTopAmount((int) animation.getAnimatedValue());
+            }
+        });
+        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
+        animator.setDuration(newDuration);
+        if (properties.delay > 0 && (previousAnimator == null
+                || previousAnimator.getAnimatedFraction() == 0)) {
+            animator.setStartDelay(properties.delay);
+        }
+        AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
+        if (listener != null) {
+            animator.addListener(listener);
+        }
+        // remove the tag when the animation is finished
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                child.setTag(TAG_ANIMATOR_TOP_INSET, null);
+                child.setTag(TAG_START_TOP_INSET, null);
+                child.setTag(TAG_END_TOP_INSET, null);
+            }
+        });
+        startAnimator(animator, listener);
+        child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
+        child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
+        child.setTag(TAG_END_TOP_INSET, newEndValue);
+    }
+
+    /**
+     * Get the end value of the height animation running on a view or the actualHeight
+     * if no animation is running.
+     */
+    public static int getFinalActualHeight(ExpandableView view) {
+        if (view == null) {
+            return 0;
+        }
+        ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
+        if (heightAnimator == null) {
+            return view.getActualHeight();
+        } else {
+            return getChildTag(view, TAG_END_HEIGHT);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index d7920a9..26e1342 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -185,6 +185,11 @@
     }
 
     @Override
+    public boolean hasOverlappingRendering() {
+        return false;
+    }
+
+    @Override
     public boolean pointInView(float localX, float localY, float slop) {
         return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
                 localY < (mRealHeight + slop);
@@ -207,6 +212,7 @@
         mDividers.add(newIndex, divider);
 
         updateGroupOverflow();
+        row.setIconTransformationAmount(0, false /* isLastChild */);
     }
 
     public void removeNotification(ExpandableNotificationRow row) {
@@ -412,7 +418,7 @@
      * @param resultState the state to update
      * @param parentState the state of the parent
      */
-    public void getState(StackScrollState resultState, StackViewState parentState) {
+    public void getState(StackScrollState resultState, ExpandableViewState parentState) {
         int childCount = mChildren.size();
         int yPosition = mNotificationHeaderMargin;
         boolean firstChild = true;
@@ -449,7 +455,7 @@
                 firstChild = false;
             }
 
-            StackViewState childState = resultState.getViewStateForView(child);
+            ExpandableViewState childState = resultState.getViewStateForView(child);
             int intrinsicHeight = child.getIntrinsicHeight();
             if (childrenExpanded) {
                 // When a group is expanded and moving into bottom stack, the bottom visible child
@@ -530,7 +536,7 @@
      * @return true if children after this one should be hidden.
      */
     private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child,
-            int parentHeight, StackViewState childState, int yPosition) {
+            int parentHeight, ExpandableViewState childState, int yPosition) {
         final int top = yPosition + child.getClipTopAmount();
         final int intrinsicHeight = child.getIntrinsicHeight();
         final int bottom = top + intrinsicHeight;
@@ -570,8 +576,8 @@
                 || mNotificationParent.isGroupExpansionChanging();
         for (int i = 0; i < childCount; i++) {
             ExpandableNotificationRow child = mChildren.get(i);
-            StackViewState viewState = state.getViewStateForView(child);
-            state.applyState(child, viewState);
+            ExpandableViewState viewState = state.getViewStateForView(child);
+            viewState.applyToView(child);
 
             // layout the divider
             View divider = mDividers.get(i);
@@ -584,16 +590,16 @@
             }
             tmpState.hidden = !dividersVisible;
             tmpState.alpha = alpha;
-            state.applyViewState(divider, tmpState);
+            tmpState.applyToView(divider);
             // There is no fake shadow to be drawn on the children
             child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
         }
-        if (mOverflowNumber != null) {
-            state.applyViewState(mOverflowNumber, mGroupOverFlowState);
+        if (mGroupOverFlowState != null) {
+            mGroupOverFlowState.applyToView(mOverflowNumber);
             mNeverAppliedGroupState = false;
         }
-        if (mNotificationHeader != null) {
-            state.applyViewState(mNotificationHeader, mHeaderViewState);
+        if (mHeaderViewState != null) {
+            mHeaderViewState.applyToView(mNotificationHeader);
         }
     }
 
@@ -608,8 +614,7 @@
         return;
     }
 
-    public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator,
-            long baseDelay, long duration) {
+    public void startAnimationToState(StackScrollState state, AnimationProperties properties) {
         int childCount = mChildren.size();
         ViewState tmpState = new ViewState();
         float expandFraction = getGroupExpandFraction();
@@ -617,8 +622,8 @@
                 || mNotificationParent.isGroupExpansionChanging();
         for (int i = childCount - 1; i >= 0; i--) {
             ExpandableNotificationRow child = mChildren.get(i);
-            StackViewState viewState = state.getViewStateForView(child);
-            stateAnimator.startStackAnimations(child, viewState, state, -1, baseDelay);
+            ExpandableViewState viewState = state.getViewStateForView(child);
+            viewState.animateTo(child, properties);
 
             // layout the divider
             View divider = mDividers.get(i);
@@ -631,7 +636,7 @@
             }
             tmpState.hidden = !dividersVisible;
             tmpState.alpha = alpha;
-            stateAnimator.startViewAnimations(divider, tmpState, baseDelay, duration);
+            tmpState.animateTo(divider, properties);
             // There is no fake shadow to be drawn on the children
             child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
         }
@@ -639,15 +644,14 @@
             if (mNeverAppliedGroupState) {
                 float alpha = mGroupOverFlowState.alpha;
                 mGroupOverFlowState.alpha = 0;
-                state.applyViewState(mOverflowNumber, mGroupOverFlowState);
+                mGroupOverFlowState.applyToView(mOverflowNumber);
                 mGroupOverFlowState.alpha = alpha;
                 mNeverAppliedGroupState = false;
             }
-            stateAnimator.startViewAnimations(mOverflowNumber, mGroupOverFlowState,
-                    baseDelay, duration);
+            mGroupOverFlowState.animateTo(mOverflowNumber, properties);
         }
         if (mNotificationHeader != null) {
-            state.applyViewState(mNotificationHeader, mHeaderViewState);
+            mHeaderViewState.applyToView(mNotificationHeader);
         }
     }
 
@@ -876,4 +880,13 @@
         }
         return 0;
     }
+
+    public void setIconsVisible(boolean iconsVisible) {
+        if (mNotificationHeaderWrapper != null) {
+            NotificationHeaderView header = mNotificationHeaderWrapper.getNotificationHeader();
+            if (header != null) {
+                header.getIcon().setForceHidden(!iconsVisible);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index 72a0e59..49da793 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -69,9 +69,9 @@
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationGuts;
-import com.android.systemui.statusbar.NotificationOverflowContainer;
 import com.android.systemui.statusbar.NotificationSettingsIconRow;
 import com.android.systemui.statusbar.NotificationSettingsIconRow.SettingsIconRowListener;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StackScrollerDecorView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.FakeShadowView;
@@ -135,8 +135,6 @@
     private Paint mDebugPaint;
     private int mContentHeight;
     private int mCollapsedSize;
-    private int mBottomStackSlowDownHeight;
-    private int mBottomStackPeekSize;
     private int mPaddingBetweenElements;
     private int mIncreasedPaddingBetweenElements;
     private int mTopPadding;
@@ -151,7 +149,7 @@
      * The current State this Layout is in
      */
     private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
-    private AmbientState mAmbientState = new AmbientState();
+    private final AmbientState mAmbientState;
     private NotificationGroupManager mGroupManager;
     private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
     private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
@@ -261,18 +259,14 @@
     private boolean mTrackingHeadsUp;
     private ScrimController mScrimController;
     private boolean mForceNoOverlappingRendering;
-    private NotificationOverflowContainer mOverflowContainer;
     private final ArrayList<Pair<ExpandableNotificationRow, Boolean>> mTmpList = new ArrayList<>();
     private FalsingManager mFalsingManager;
     private boolean mAnimationRunning;
-    private ViewTreeObserver.OnPreDrawListener mBackgroundUpdater
+    private ViewTreeObserver.OnPreDrawListener mRunningAnimationUpdater
             = new ViewTreeObserver.OnPreDrawListener() {
         @Override
         public boolean onPreDraw() {
-            // if it needs animation
-            if (!mNeedsAnimation && !mChildrenUpdateRequested) {
-                updateBackground();
-            }
+            onPreDrawDuringAnimation();
             return true;
         }
     };
@@ -334,7 +328,7 @@
     private boolean mPulsing;
     private boolean mDrawBackgroundAsSrc;
     private boolean mFadingOut;
-    private boolean mParentFadingOut;
+    private boolean mParentNotFullyVisible;
     private boolean mGroupExpandedForMeasure;
     private boolean mScrollable;
     private View mForcedScroll;
@@ -354,6 +348,15 @@
     private boolean mQsExpanded;
     private boolean mForwardScrollable;
     private boolean mBackwardScrollable;
+    private NotificationShelf mShelf;
+    private int mMaxDisplayedNotifications = -1;
+    private int mStatusBarHeight;
+    private boolean mNoAmbient;
+    private final Rect mClipRect = new Rect();
+    private boolean mIsClipped;
+    private Rect mRequestedClipBounds;
+    private boolean mInHeadsUpPinnedMode;
+    private boolean mHeadsUpAnimatingAway;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -370,6 +373,7 @@
     public NotificationStackScrollLayout(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        mAmbientState = new AmbientState(context);
         mBgColor = context.getColor(R.color.notification_shade_background_color);
         int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
         int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
@@ -418,11 +422,6 @@
         if (DEBUG) {
             int y = mTopPadding;
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-            y = (int) (getLayoutHeight() - mBottomStackPeekSize
-                    - mBottomStackSlowDownHeight);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
-            y = (int) (getLayoutHeight() - mBottomStackPeekSize);
-            canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
             y = (int) getLayoutHeight();
             canvas.drawLine(0, y, getWidth(), y, mDebugPaint);
             y = getHeight() - getEmptyBottomMargin();
@@ -459,16 +458,15 @@
         mOverflingDistance = configuration.getScaledOverflingDistance();
         mCollapsedSize = context.getResources()
                 .getDimensionPixelSize(R.dimen.notification_min_height);
-        mBottomStackPeekSize = context.getResources()
-                .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
         mStackScrollAlgorithm.initView(context);
+        mAmbientState.reload(context);
         mPaddingBetweenElements = Math.max(1, context.getResources()
                 .getDimensionPixelSize(R.dimen.notification_divider_height));
         mIncreasedPaddingBetweenElements = context.getResources()
                 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
-        mBottomStackSlowDownHeight = mStackScrollAlgorithm.getBottomStackSlowDownLength();
         mMinTopOverScrollToEscape = getResources().getDimensionPixelSize(
                 R.dimen.min_top_overscroll_to_qs);
+        mStatusBarHeight = getResources().getDimensionPixelOffset(R.dimen.status_bar_height);
     }
 
     public void setDrawBackgroundAsSrc(boolean asSrc) {
@@ -477,7 +475,7 @@
     }
 
     private void updateSrcDrawing() {
-        mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && (!mFadingOut && !mParentFadingOut)
+        mBackgroundPaint.setXfermode(mDrawBackgroundAsSrc && !mFadingOut && !mParentNotFullyVisible
                 ? mSrcMode : null);
         invalidate();
     }
@@ -529,8 +527,9 @@
         }
     }
 
-    public void updateSpeedBumpIndex(int newIndex) {
+    public void updateSpeedBumpIndex(int newIndex, boolean noAmbient) {
         mAmbientState.setSpeedBumpIndex(newIndex);
+        mNoAmbient = noAmbient;
     }
 
     public void setChildLocationsChangedListener(OnChildLocationsChangedListener listener) {
@@ -541,21 +540,22 @@
      * Returns the location the given child is currently rendered at.
      *
      * @param child the child to get the location for
-     * @return one of {@link StackViewState}'s <code>LOCATION_*</code> constants
+     * @return one of {@link ExpandableViewState}'s <code>LOCATION_*</code> constants
      */
     public int getChildLocation(View child) {
-        StackViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
+        ExpandableViewState childViewState = mCurrentStackScrollState.getViewStateForView(child);
         if (childViewState == null) {
-            return StackViewState.LOCATION_UNKNOWN;
+            return ExpandableViewState.LOCATION_UNKNOWN;
         }
         if (childViewState.gone) {
-            return StackViewState.LOCATION_GONE;
+            return ExpandableViewState.LOCATION_GONE;
         }
         return childViewState.location;
     }
 
     private void setMaxLayoutHeight(int maxLayoutHeight) {
         mMaxLayoutHeight = maxLayoutHeight;
+        mShelf.setMaxLayoutHeight(maxLayoutHeight);
         updateAlgorithmHeightAndPadding();
     }
 
@@ -584,6 +584,13 @@
         }
     }
 
+    private void onPreDrawDuringAnimation() {
+        mShelf.updateAppearance();
+        if (!mNeedsAnimation && !mChildrenUpdateRequested) {
+            updateBackground();
+        }
+    }
+
     private void updateScrollStateForAddedChildren() {
         if (mChildrenToAddAnimated.isEmpty()) {
             return;
@@ -671,7 +678,18 @@
      */
     public void setExpandedHeight(float height) {
         mExpandedHeight = height;
-        setIsExpanded(height > 0.0f);
+        setIsExpanded(height > 0);
+        int minExpansionHeight = getMinExpansionHeight();
+        if (height < minExpansionHeight) {
+            mClipRect.left = 0;
+            mClipRect.right = getWidth();
+            mClipRect.top = 0;
+            mClipRect.bottom = (int) height;
+            height = minExpansionHeight;
+            setRequestedClipBounds(mClipRect);
+        } else {
+            setRequestedClipBounds(null);
+        }
         int stackHeight;
         float translationY;
         float appearEndPosition = getAppearEndPosition();
@@ -697,6 +715,26 @@
             requestChildrenUpdate();
         }
         setStackTranslation(translationY);
+        requestChildrenUpdate();
+    }
+
+    private void setRequestedClipBounds(Rect clipRect) {
+        mRequestedClipBounds = clipRect;
+        updateClipping();
+    }
+
+    public void updateClipping() {
+        boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
+                && !mHeadsUpAnimatingAway;
+        if (mIsClipped != clipped) {
+            mIsClipped = clipped;
+            updateFadingState();
+        }
+        if (clipped) {
+            setClipBounds(mRequestedClipBounds);
+        } else {
+            setClipBounds(null);
+        }
     }
 
     /**
@@ -704,13 +742,7 @@
      *         Measured relative to the resting position.
      */
     private float getExpandTranslationStart() {
-        int startPosition = 0;
-        if (!mTrackingHeadsUp && !mHeadsUpManager.hasPinnedHeadsUp()) {
-            startPosition = - Math.min(getFirstChildIntrinsicHeight(),
-                    mMaxLayoutHeight - mIntrinsicPadding - mBottomStackSlowDownHeight
-                            - mBottomStackPeekSize);
-        }
-        return startPosition - mTopPadding;
+        return - mTopPadding;
     }
 
     /**
@@ -718,9 +750,14 @@
      *         Measured in absolute height.
      */
     private float getAppearStartPosition() {
-        return mTrackingHeadsUp
-                ? mHeadsUpManager.getTopHeadsUpPinnedHeight()
-                : 0;
+        if (mTrackingHeadsUp && mFirstVisibleBackgroundChild != null) {
+            if (mFirstVisibleBackgroundChild.isAboveShelf()) {
+                // If we ever expanded beyond the first notification, it's allowed to merge into
+                // the shelf
+                return mFirstVisibleBackgroundChild.getPinnedHeadsUpHeight();
+            }
+        }
+        return getMinExpansionHeight();
     }
 
     /**
@@ -728,11 +765,16 @@
      *         Measured in absolute height.
      */
     private float getAppearEndPosition() {
-        int firstItemHeight = mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()
-                ? mHeadsUpManager.getTopHeadsUpPinnedHeight() + mBottomStackPeekSize
-                        + mBottomStackSlowDownHeight
-                : getLayoutMinHeight();
-        return firstItemHeight + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
+        int appearPosition;
+        if (mTrackingHeadsUp || mHeadsUpManager.hasPinnedHeadsUp()) {
+            appearPosition = mHeadsUpManager.getTopHeadsUpPinnedHeight();
+        } else {
+            appearPosition = getFirstItemMinHeight();
+        }
+        if (getNotGoneChildCount() > 1) {
+            appearPosition += mShelf.getIntrinsicHeight();
+        }
+        return appearPosition + (onKeyguard() ? mTopPadding : mIntrinsicPadding);
     }
 
     /**
@@ -773,14 +815,6 @@
         return firstChild != null ? firstChild.getMinHeight() : mCollapsedSize;
     }
 
-    public int getBottomStackPeekSize() {
-        return mBottomStackPeekSize;
-    }
-
-    public int getBottomStackSlowDownHeight() {
-        return mBottomStackSlowDownHeight;
-    }
-
     public void setLongPressListener(SwipeHelper.LongPressListener listener) {
         mSwipeHelper.setLongPressListener(listener);
         mLongPressListener = listener;
@@ -957,7 +991,8 @@
             }
             float childTop = slidingChild.getTranslationY();
             float top = childTop + slidingChild.getClipTopAmount();
-            float bottom = childTop + slidingChild.getActualHeight();
+            float bottom = childTop + slidingChild.getActualHeight()
+                    - slidingChild.getClipBottomAmount();
 
             float dist = Math.min(Math.abs(top - localTouchY), Math.abs(bottom - localTouchY));
             if (dist < minDist) {
@@ -986,7 +1021,8 @@
             }
             float childTop = slidingChild.getTranslationY();
             float top = childTop + slidingChild.getClipTopAmount();
-            float bottom = childTop + slidingChild.getActualHeight();
+            float bottom = childTop + slidingChild.getActualHeight()
+                    - slidingChild.getClipBottomAmount();
 
             // Allow the full width of this view to prevent gesture conflict on Keyguard (phone and
             // camera affordance).
@@ -1072,7 +1108,7 @@
                     row.getStatusBarNotification());
             mGroupExpandedForMeasure = false;
             row.setForceUnlocked(false);
-            StackViewState viewState = mCurrentStackScrollState.getViewStateForView(view);
+            ExpandableViewState viewState = mCurrentStackScrollState.getViewStateForView(view);
             if (viewState != null) {
                 // The view could have been removed
                 return Math.min(viewState.height, maxContentHeight);
@@ -1748,8 +1784,7 @@
 
     private int getScrollRange() {
         int contentHeight = getContentHeight();
-        int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight + mBottomStackPeekSize
-                + mBottomStackSlowDownHeight);
+        int scrollRange = Math.max(0, contentHeight - mMaxLayoutHeight);
         int imeInset = getImeInset();
         scrollRange += Math.min(imeInset, Math.max(0,
                 getContentHeight() - (getHeight() - imeInset)));
@@ -1767,7 +1802,7 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child.getVisibility() != View.GONE) {
+            if (child.getVisibility() != View.GONE && child != mShelf) {
                 return (ExpandableView) child;
             }
         }
@@ -1814,7 +1849,7 @@
         int childCount = getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
             View child = getChildAt(i);
-            if (child.getVisibility() != View.GONE) {
+            if (child.getVisibility() != View.GONE && child != mShelf) {
                 return child;
             }
         }
@@ -1829,7 +1864,7 @@
         int count = 0;
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = (ExpandableView) getChildAt(i);
-            if (child.getVisibility() != View.GONE && !child.willBeGone()) {
+            if (child.getVisibility() != View.GONE && !child.willBeGone() && child != mShelf) {
                 count++;
             }
         }
@@ -1843,9 +1878,17 @@
     private void updateContentHeight() {
         int height = 0;
         float previousIncreasedAmount = 0.0f;
+        int numShownItems = 0;
+        boolean finish = false;
         for (int i = 0; i < getChildCount(); i++) {
             ExpandableView expandableView = (ExpandableView) getChildAt(i);
-            if (expandableView.getVisibility() != View.GONE) {
+            if (expandableView.getVisibility() != View.GONE
+                    && !expandableView.hasNoContentHeight()) {
+                if (mMaxDisplayedNotifications != -1
+                        && numShownItems >= mMaxDisplayedNotifications) {
+                    expandableView = mShelf;
+                    finish = true;
+                }
                 float increasedPaddingAmount = expandableView.getIncreasedPaddingAmount();
                 if (height != 0) {
                     height += (int) NotificationUtils.interpolate(
@@ -1855,10 +1898,15 @@
                 }
                 previousIncreasedAmount = increasedPaddingAmount;
                 height += expandableView.getIntrinsicHeight();
+                numShownItems++;
+                if (finish) {
+                    break;
+                }
             }
         }
         mContentHeight = height + mTopPadding;
         updateScrollability();
+        mAmbientState.setLayoutMaxHeight(mContentHeight);
     }
 
     private void updateScrollability() {
@@ -2036,7 +2084,7 @@
 
     private void applyCurrentBackgroundBounds() {
         mScrimController.setExcludedBackgroundArea(
-                mFadingOut || mParentFadingOut || mAmbientState.isDark() ? null
+                mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
                         : mCurrentBounds);
         invalidate();
     }
@@ -2056,7 +2104,7 @@
         ActivatableNotificationView firstView = mFirstVisibleBackgroundChild;
         int top = 0;
         if (firstView != null) {
-            int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(firstView);
+            int finalTranslationY = (int) ViewState.getFinalTranslationY(firstView);
             if (mAnimateNextBackgroundTop
                     || mTopAnimator == null && mCurrentBounds.top == finalTranslationY
                     || mTopAnimator != null && mEndAnimationRect.top == finalTranslationY) {
@@ -2066,11 +2114,18 @@
                 top = (int) firstView.getTranslationY();
             }
         }
-        ActivatableNotificationView lastView = mLastVisibleBackgroundChild;
+        ActivatableNotificationView lastView = mShelf.hasItemsInStableShelf()
+                ? mShelf
+                : mLastVisibleBackgroundChild;
         int bottom = 0;
         if (lastView != null) {
-            int finalTranslationY = (int) StackStateAnimator.getFinalTranslationY(lastView);
-            int finalHeight = StackStateAnimator.getFinalActualHeight(lastView);
+            int finalTranslationY;
+            if (lastView == mShelf) {
+                finalTranslationY = (int) mShelf.getTranslationY();
+            } else {
+                finalTranslationY = (int) ViewState.getFinalTranslationY(lastView);
+            }
+            int finalHeight = ExpandableViewState.getFinalActualHeight(lastView);
             int finalBottom = finalTranslationY + finalHeight;
             finalBottom = Math.min(finalBottom, getHeight());
             if (mAnimateNextBackgroundBottom
@@ -2082,6 +2137,7 @@
                 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
                 bottom = Math.min(bottom, getHeight());
             }
+            bottom -= lastView.getClipBottomAmount();
         } else {
             top = mTopPadding;
             bottom = top;
@@ -2115,8 +2171,8 @@
         int childCount = getChildCount();
         for (int i = childCount - 1; i >= 0; i--) {
             View child = getChildAt(i);
-            if (child.getVisibility() != View.GONE
-                    && child instanceof ActivatableNotificationView) {
+            if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
+                    && child != mShelf) {
                 return (ActivatableNotificationView) child;
             }
         }
@@ -2127,8 +2183,8 @@
         int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
-            if (child.getVisibility() != View.GONE
-                    && child instanceof ActivatableNotificationView) {
+            if (child.getVisibility() != View.GONE && child instanceof ActivatableNotificationView
+                    && child != mShelf) {
                 return (ActivatableNotificationView) child;
             }
         }
@@ -2211,9 +2267,7 @@
     }
 
     public int getLayoutMinHeight() {
-        int firstChildMinHeight = getFirstChildIntrinsicHeight();
-        return Math.min(firstChildMinHeight + mBottomStackPeekSize + mBottomStackSlowDownHeight,
-                mMaxLayoutHeight - mIntrinsicPadding);
+        return mShelf.getIntrinsicHeight();
     }
 
     public int getFirstChildIntrinsicHeight() {
@@ -2237,8 +2291,11 @@
         final ExpandableView firstChild = getFirstChildNotGone();
         final int firstChildMinHeight = firstChild != null ? firstChild.getCollapsedHeight()
                 : mCollapsedSize;
-        return mIntrinsicPadding + firstChildMinHeight + mBottomStackPeekSize
-                + mBottomStackSlowDownHeight;
+        int shelfHeight = 0;
+        if (mLastVisibleBackgroundChild != null) {
+            shelfHeight = mShelf.getIntrinsicHeight();
+        }
+        return mIntrinsicPadding + firstChildMinHeight + shelfHeight;
     }
 
     private int clampPadding(int desiredPadding) {
@@ -2467,7 +2524,7 @@
         if (hasAddEvent) {
             // This child was just added lets remove all events.
             mHeadsUpChangeAnimations.removeAll(mTmpList);
-            ((ExpandableNotificationRow ) child).setHeadsupDisappearRunning(false);
+            ((ExpandableNotificationRow ) child).setHeadsUpAnimatingAway(false);
         }
         mTmpList.clear();
         return hasAddEvent;
@@ -2536,7 +2593,7 @@
         for (int i = 0; i < getChildCount(); i++) {
             ExpandableView child = (ExpandableView) getChildAt(i);
             boolean notGone = child.getVisibility() != View.GONE;
-            if (notGone) {
+            if (notGone && !child.hasNoContentHeight()) {
                 float increasedPaddingAmount = child.getIncreasedPaddingAmount();
                 if (position != 0) {
                     position += (int) NotificationUtils.interpolate(
@@ -2577,6 +2634,7 @@
         }
         mFirstVisibleBackgroundChild = firstChild;
         mLastVisibleBackgroundChild = lastChild;
+        mAmbientState.setLastVisibleBackgroundChild(lastChild);
     }
 
     private void onViewAddedInternal(View child) {
@@ -2727,10 +2785,10 @@
                         : AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR;
                 if (row.isChildInGroup()) {
                     // We can otherwise get stuck in there if it was just isolated
-                    row.setHeadsupDisappearRunning(false);
+                    row.setHeadsUpAnimatingAway(false);
                 }
             } else {
-                StackViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
+                ExpandableViewState viewState = mCurrentStackScrollState.getViewStateForView(row);
                 if (viewState == null) {
                     // A view state was never generated for this view, so we don't need to animate
                     // this. This may happen with notification children.
@@ -2755,7 +2813,7 @@
         mAddedHeadsUpChildren.clear();
     }
 
-    private boolean shouldHunAppearFromBottom(StackViewState viewState) {
+    private boolean shouldHunAppearFromBottom(ExpandableViewState viewState) {
         if (viewState.yTranslation + viewState.height < mAmbientState.getMaxHeadsUpTranslation()) {
             return false;
         }
@@ -3078,14 +3136,7 @@
     }
 
     public int getEmptyBottomMargin() {
-        int emptyMargin = mMaxLayoutHeight - mContentHeight - mBottomStackPeekSize
-                - mBottomStackSlowDownHeight;
-        return Math.max(emptyMargin, 0);
-    }
-
-    public float getKeyguardBottomStackSize() {
-        return mBottomStackPeekSize + getResources().getDimensionPixelSize(
-                R.dimen.bottom_stack_slow_down_length);
+        return Math.max(mMaxLayoutHeight - mContentHeight, 0);
     }
 
     public void onExpansionStarted() {
@@ -3195,20 +3246,18 @@
                 if (row.isChildInGroup()) {
                     endPosition += row.getNotificationParent().getTranslationY();
                 }
-                int stackEnd = getStackEndPosition();
-                if (endPosition > stackEnd) {
-                    setOwnScrollY((int) (mOwnScrollY + endPosition - stackEnd));
+                int layoutEnd = mMaxLayoutHeight + (int) mStackTranslation;
+                if (row != mLastVisibleBackgroundChild) {
+                    layoutEnd -= mShelf.getIntrinsicHeight() + mPaddingBetweenElements;
+                }
+                if (endPosition > layoutEnd) {
+                    setOwnScrollY((int) (mOwnScrollY + endPosition - layoutEnd));
                     mDisallowScrollingInThisMotion = true;
                 }
             }
         }
     }
 
-    private int getStackEndPosition() {
-        return mMaxLayoutHeight - mBottomStackPeekSize - mBottomStackSlowDownHeight
-                + mPaddingBetweenElements + (int) mStackTranslation;
-    }
-
     public void setOnHeightChangedListener(
             ExpandableView.OnHeightChangedListener mOnHeightChangedListener) {
         this.mOnHeightChangedListener = mOnHeightChangedListener;
@@ -3231,10 +3280,10 @@
             View view = getChildAt(i);
             if (view instanceof ExpandableNotificationRow) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
-                row.setHeadsupDisappearRunning(false);
+                row.setHeadsUpAnimatingAway(false);
                 if (row.isSummaryWithChildren()) {
                     for (ExpandableNotificationRow child : row.getNotificationChildren()) {
-                        child.setHeadsupDisappearRunning(false);
+                        child.setHeadsUpAnimatingAway(false);
                     }
                 }
             }
@@ -3538,50 +3587,6 @@
         }
     }
 
-    public void setOverflowContainer(NotificationOverflowContainer overFlowContainer) {
-        int index = -1;
-        if (mOverflowContainer != null) {
-            index = indexOfChild(mOverflowContainer);
-            removeView(mOverflowContainer);
-        }
-        mOverflowContainer = overFlowContainer;
-        addView(mOverflowContainer, index);
-    }
-
-    public void updateOverflowContainerVisibility(boolean visible) {
-        int oldVisibility = mOverflowContainer.willBeGone() ? GONE
-                : mOverflowContainer.getVisibility();
-        final int newVisibility = visible ? VISIBLE : GONE;
-        if (oldVisibility != newVisibility) {
-            Runnable onFinishedRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    mOverflowContainer.setVisibility(newVisibility);
-                    mOverflowContainer.setWillBeGone(false);
-                    updateContentHeight();
-                    notifyHeightChangeListener(mOverflowContainer);
-                }
-            };
-            if (!mAnimationsEnabled || !mIsExpanded) {
-                mOverflowContainer.cancelAppearDrawing();
-                onFinishedRunnable.run();
-            } else if (newVisibility != GONE) {
-                mOverflowContainer.performAddAnimation(0,
-                        StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                mOverflowContainer.setVisibility(newVisibility);
-                mOverflowContainer.setWillBeGone(false);
-                updateContentHeight();
-                notifyHeightChangeListener(mOverflowContainer);
-            } else {
-                mOverflowContainer.performRemoveAnimation(
-                        StackStateAnimator.ANIMATION_DURATION_STANDARD,
-                        0.0f,
-                        onFinishedRunnable);
-                mOverflowContainer.setWillBeGone(true);
-            }
-        }
-    }
-
     public void updateDismissView(boolean visible) {
         int oldVisibility = mDismissView.willBeGone() ? GONE : mDismissView.getVisibility();
         int newVisibility = visible ? VISIBLE : GONE;
@@ -3663,7 +3668,8 @@
             if (child.getVisibility() == GONE) {
                 continue;
             }
-            float bottom = child.getTranslationY() + child.getActualHeight();
+            float bottom = child.getTranslationY() + child.getActualHeight()
+                    - child.getClipBottomAmount();
             if (bottom > max) {
                 max = bottom;
             }
@@ -3701,7 +3707,8 @@
                     // we are above a notification entirely let's abort
                     return false;
                 }
-                boolean belowChild = touchY > childTop + child.getActualHeight();
+                boolean belowChild = touchY > childTop + child.getActualHeight()
+                        - child.getClipBottomAmount();
                 if (child == mDismissView) {
                     if(!belowChild && !mDismissView.isOnEmptySpace(touchX - mDismissView.getX(),
                                     touchY - childTop)) {
@@ -3795,7 +3802,7 @@
                 // fall through
             case android.R.id.accessibilityActionScrollUp:
                 final int viewportHeight = getHeight() - mPaddingBottom - mTopPadding - mPaddingTop
-                        - mBottomStackPeekSize - mBottomStackSlowDownHeight;
+                        - mShelf.getIntrinsicHeight();
                 final int targetScrollY = Math.max(0,
                         Math.min(mOwnScrollY + direction * viewportHeight, getScrollRange()));
                 if (targetScrollY != mOwnScrollY) {
@@ -3835,7 +3842,7 @@
             mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
             mNeedsAnimation = true;
             if (!mIsExpanded && !isHeadsUp) {
-                row.setHeadsupDisappearRunning(true);
+                row.setHeadsUpAnimatingAway(true);
             }
             requestChildrenUpdate();
         }
@@ -3885,9 +3892,9 @@
     public void setAnimationRunning(boolean animationRunning) {
         if (animationRunning != mAnimationRunning) {
             if (animationRunning) {
-                getViewTreeObserver().addOnPreDrawListener(mBackgroundUpdater);
+                getViewTreeObserver().addOnPreDrawListener(mRunningAnimationUpdater);
             } else {
-                getViewTreeObserver().removeOnPreDrawListener(mBackgroundUpdater);
+                getViewTreeObserver().removeOnPreDrawListener(mRunningAnimationUpdater);
             }
             mAnimationRunning = animationRunning;
             updateContinuousShadowDrawing();
@@ -3910,9 +3917,13 @@
         }
     }
 
-    public void setParentFadingOut(boolean fadingOut) {
-        if (fadingOut != mParentFadingOut) {
-            mParentFadingOut = fadingOut;
+    public void setParentNotFullyVisible(boolean parentNotFullyVisible) {
+        if (mScrimController == null) {
+            // we're not set up yet.
+            return;
+        }
+        if (parentNotFullyVisible != mParentNotFullyVisible) {
+            mParentNotFullyVisible = parentNotFullyVisible;
             updateFadingState();
         }
     }
@@ -3949,6 +3960,45 @@
         }
     }
 
+    public void setShelf(NotificationShelf shelf) {
+        mShelf = shelf;
+        int index = -1;
+        if (mShelf != null) {
+            index = indexOfChild(mShelf);
+            removeView(mShelf);
+        }
+        addView(mShelf, index);
+        mAmbientState.setShelf(shelf);
+        mStateAnimator.setShelf(shelf);
+        shelf.bind(mAmbientState, this);
+    }
+
+    public NotificationShelf getNotificationShelf() {
+        return mShelf;
+    }
+
+    public void setMaxDisplayedNotifications(int maxDisplayedNotifications) {
+        if (mMaxDisplayedNotifications != maxDisplayedNotifications) {
+            mMaxDisplayedNotifications = maxDisplayedNotifications;
+            updateContentHeight();
+            notifyHeightChangeListener(mShelf);
+        }
+    }
+
+    public int getMinExpansionHeight() {
+        return mShelf.getIntrinsicHeight() - (mShelf.getIntrinsicHeight() - mStatusBarHeight) / 2;
+    }
+
+    public void setInHeadsUpPinnedMode(boolean inHeadsUpPinnedMode) {
+        mInHeadsUpPinnedMode = inHeadsUpPinnedMode;
+        updateClipping();
+    }
+
+    public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+        mHeadsUpAnimatingAway = headsUpAnimatingAway;
+        updateClipping();
+    }
+
     /**
      * A listener that is notified when some child locations might have changed.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/PiecewiseLinearIndentationFunctor.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/PiecewiseLinearIndentationFunctor.java
deleted file mode 100644
index 1c37c35..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/PiecewiseLinearIndentationFunctor.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2014 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.statusbar.stack;
-
-import java.util.ArrayList;
-
-/**
- * A Functor which interpolates the stack distance linearly based on base values.
- * The base values are based on an interpolation between a linear function and a
- * quadratic function
- */
-public class PiecewiseLinearIndentationFunctor extends StackIndentationFunctor {
-
-    private final ArrayList<Float> mBaseValues;
-    private final float mLinearPart;
-
-    /**
-     * @param maxItemsInStack The maximum number of items which should be visible at the same time,
-     *                        i.e the function returns totalTransitionDistance for the element with
-     *                        index maxItemsInStack
-     * @param peekSize The visual appearance of this is how far the cards in the stack peek
-     *                 out below the top card and it is measured in real pixels.
-     *                 Note that the visual appearance does not necessarily always correspond to
-     *                 the actual visual distance below the top card but is a maximum,
-     *                 achieved when the next card just starts transitioning into the stack and
-     *                 the stack is full.
-     *                 If distanceToPeekStart is 0, we directly start at the peek, otherwise the
-     *                 first element transitions between 0 and distanceToPeekStart.
-     *                 Visualization:
-     *           ---------------------------------------------------   ---
-     *          |                                                   |   |
-     *          |                  FIRST ITEM                       |   | <- distanceToPeekStart
-     *          |                                                   |   |
-     *          |---------------------------------------------------|  ---  ---
-     *          |__________________SECOND ITEM______________________|        |  <- peekSize
-     *          |===================================================|       _|_
-     *
-     * @param distanceToPeekStart The distance to the start of the peak.
-     * @param linearPart The interpolation factor between the linear and the quadratic amount taken.
-     *                   This factor must be somewhere in [0 , 1]
-     */
-    PiecewiseLinearIndentationFunctor(int maxItemsInStack,
-                                      int peekSize,
-                                      int distanceToPeekStart,
-                                      float linearPart) {
-        super(maxItemsInStack, peekSize, distanceToPeekStart);
-        mBaseValues = new ArrayList<Float>(maxItemsInStack+1);
-        initBaseValues();
-        mLinearPart = linearPart;
-    }
-
-    private void initBaseValues() {
-        int sumOfSquares = getSumOfSquares(mMaxItemsInStack-1);
-        int totalWeight = 0;
-        mBaseValues.add(0.0f);
-        for (int i = 0; i < mMaxItemsInStack - 1; i++) {
-            totalWeight += (mMaxItemsInStack - i - 1) * (mMaxItemsInStack - i - 1);
-            mBaseValues.add((float) totalWeight / sumOfSquares);
-        }
-    }
-
-    /**
-     * Get the sum of squares up to and including n, i.e sum(i * i, 1, n)
-     *
-     * @param n the maximum square to include
-     * @return
-     */
-    private int getSumOfSquares(int n) {
-        return n * (n + 1) * (2 * n + 1) / 6;
-    }
-
-    @Override
-    public float getValue(float itemsBefore) {
-        if (mStackStartsAtPeek) {
-            // We directly start at the stack, so no initial interpolation.
-            itemsBefore++;
-        }
-        if (itemsBefore < 0) {
-            return 0;
-        } else if (itemsBefore >= mMaxItemsInStack) {
-            return mTotalTransitionDistance;
-        }
-        int below = (int) itemsBefore;
-        float partialIn = itemsBefore - below;
-
-        if (below == 0) {
-            return mDistanceToPeekStart * partialIn;
-        } else {
-            float result = mDistanceToPeekStart;
-            float progress = mBaseValues.get(below - 1) * (1 - partialIn)
-                    + mBaseValues.get(below) * partialIn;
-            result += (progress * (1 - mLinearPart)
-                    + (itemsBefore - 1) / (mMaxItemsInStack - 1)  * mLinearPart) * mPeekSize;
-            return result;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackIndentationFunctor.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackIndentationFunctor.java
deleted file mode 100644
index 034eba6..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackIndentationFunctor.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2014 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.statusbar.stack;
-
-/**
- * A functor which can be queried for offset given the number of items before it.
- */
-public abstract class StackIndentationFunctor {
-
-    protected int mTotalTransitionDistance;
-    protected int mDistanceToPeekStart;
-    protected int mMaxItemsInStack;
-    protected int mPeekSize;
-    protected boolean mStackStartsAtPeek;
-
-    /**
-     * @param maxItemsInStack The maximum number of items which should be visible at the same time,
-     *                        i.e the function returns totalTransitionDistance for the element with
-     *                        index maxItemsInStack
-     * @param peekSize The visual appearance of this is how far the cards in the stack peek
-     *                 out below the top card and it is measured in real pixels.
-     *                 Note that the visual appearance does not necessarily always correspond to
-     *                 the actual visual distance below the top card but is a maximum,
-     *                 achieved when the next card just starts transitioning into the stack and
-     *                 the stack is full.
-     *                 If distanceToPeekStart is 0, we directly start at the peek, otherwise the
-     *                 first element transitions between 0 and distanceToPeekStart.
-     *                 Visualization:
-     *           ---------------------------------------------------   ---
-     *          |                                                   |   |
-     *          |                  FIRST ITEM                       |   | <- distanceToPeekStart
-     *          |                                                   |   |
-     *          |---------------------------------------------------|  ---  ---
-     *          |__________________SECOND ITEM______________________|        |  <- peekSize
-     *          |===================================================|       _|_
-     *
-     * @param distanceToPeekStart The distance to the start of the peak.
-     */
-    StackIndentationFunctor(int maxItemsInStack, int peekSize, int distanceToPeekStart) {
-        mDistanceToPeekStart = distanceToPeekStart;
-        mStackStartsAtPeek = mDistanceToPeekStart == 0;
-        mMaxItemsInStack = maxItemsInStack;
-        mPeekSize = peekSize;
-        updateTotalTransitionDistance();
-
-    }
-
-    private void updateTotalTransitionDistance() {
-        mTotalTransitionDistance = mDistanceToPeekStart + mPeekSize;
-    }
-
-    public void setPeekSize(int mPeekSize) {
-        this.mPeekSize = mPeekSize;
-        updateTotalTransitionDistance();
-    }
-
-    public void setDistanceToPeekStart(int distanceToPeekStart) {
-        mDistanceToPeekStart = distanceToPeekStart;
-        mStackStartsAtPeek = mDistanceToPeekStart == 0;
-        updateTotalTransitionDistance();
-    }
-
-    /**
-     * Gets the offset of this Functor given a the quantity of items before it
-     *
-     * @param itemsBefore how many items are already in the stack before this element
-     * @return the offset
-     */
-    public abstract float getValue(float itemsBefore);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index c9e4eac..1dc346d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -22,9 +22,10 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.notification.FakeShadowView;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 
 import java.util.ArrayList;
@@ -40,20 +41,13 @@
 
     private static final String LOG_TAG = "StackScrollAlgorithm";
 
-    private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
-
     private int mPaddingBetweenElements;
     private int mIncreasedPaddingBetweenElements;
     private int mCollapsedSize;
-    private int mBottomStackPeekSize;
-    private int mZDistanceBetweenElements;
-    private int mZBasicHeight;
-
-    private StackIndentationFunctor mBottomStackIndentationFunctor;
 
     private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
     private boolean mIsExpanded;
-    private int mBottomStackSlowDownLength;
+    private int mStatusBarHeight;
 
     public StackScrollAlgorithm(Context context) {
         initView(context);
@@ -63,29 +57,14 @@
         initConstants(context);
     }
 
-    public int getBottomStackSlowDownLength() {
-        return mBottomStackSlowDownLength + mPaddingBetweenElements;
-    }
-
     private void initConstants(Context context) {
-        mPaddingBetweenElements = Math.max(1, context.getResources()
-                .getDimensionPixelSize(R.dimen.notification_divider_height));
+        mPaddingBetweenElements = context.getResources().getDimensionPixelSize(
+                R.dimen.notification_divider_height);
         mIncreasedPaddingBetweenElements = context.getResources()
                 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
         mCollapsedSize = context.getResources()
                 .getDimensionPixelSize(R.dimen.notification_min_height);
-        mBottomStackPeekSize = context.getResources()
-                .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
-        mZDistanceBetweenElements = Math.max(1, context.getResources()
-                .getDimensionPixelSize(R.dimen.z_distance_between_notifications));
-        mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements;
-        mBottomStackSlowDownLength = context.getResources()
-                .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length);
-        mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
-                MAX_ITEMS_IN_BOTTOM_STACK,
-                mBottomStackPeekSize,
-                getBottomStackSlowDownLength(),
-                0.5f);
+        mStatusBarHeight = context.getResources().getDimensionPixelSize(R.dimen.status_bar_height);
     }
 
     public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) {
@@ -107,7 +86,8 @@
         handleDraggedViews(ambientState, resultState, algorithmState);
         updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
         updateClipping(resultState, algorithmState, ambientState);
-        updateSpeedBumpState(resultState, algorithmState, ambientState.getSpeedBumpIndex());
+        updateSpeedBumpState(resultState, algorithmState, ambientState);
+        updateShelfState(resultState, ambientState);
         getNotificationChildrenStates(resultState, algorithmState);
     }
 
@@ -124,16 +104,22 @@
     }
 
     private void updateSpeedBumpState(StackScrollState resultState,
-            StackScrollAlgorithmState algorithmState, int speedBumpIndex) {
+            StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
+        int belowSpeedBump = ambientState.getSpeedBumpIndex();
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
-            StackViewState childViewState = resultState.getViewStateForView(child);
+            ExpandableViewState childViewState = resultState.getViewStateForView(child);
 
             // The speed bump can also be gone, so equality needs to be taken when comparing
             // indices.
-            childViewState.belowSpeedBump = speedBumpIndex != -1 && i >= speedBumpIndex;
+            childViewState.belowSpeedBump = i >= belowSpeedBump;
         }
+
+    }
+    private void updateShelfState(StackScrollState resultState, AmbientState ambientState) {
+        NotificationShelf shelf = ambientState.getShelf();
+        shelf.updateState(resultState, ambientState);
     }
 
     private void updateClipping(StackScrollState resultState,
@@ -144,7 +130,7 @@
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = algorithmState.visibleChildren.get(i);
-            StackViewState state = resultState.getViewStateForView(child);
+            ExpandableViewState state = resultState.getViewStateForView(child);
             if (!child.mustStayOnScreen()) {
                 previousNotificationEnd = Math.max(drawStart, previousNotificationEnd);
                 previousNotificationStart = Math.max(drawStart, previousNotificationStart);
@@ -195,13 +181,13 @@
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
-            StackViewState childViewState = resultState.getViewStateForView(child);
+            ExpandableViewState childViewState = resultState.getViewStateForView(child);
             childViewState.dimmed = dimmed;
             childViewState.dark = dark;
             childViewState.hideSensitive = hideSensitive;
             boolean isActivatedChild = activatedChild == child;
             if (dimmed && isActivatedChild) {
-                childViewState.zTranslation += 2.0f * mZDistanceBetweenElements;
+                childViewState.zTranslation += 2.0f * ambientState.getZDistanceBetweenElements();
             }
         }
     }
@@ -219,7 +205,7 @@
                 if (!draggedViews.contains(nextChild)) {
                     // only if the view is not dragged itself we modify its state to be fully
                     // visible
-                    StackViewState viewState = resultState.getViewStateForView(
+                    ExpandableViewState viewState = resultState.getViewStateForView(
                             nextChild);
                     // The child below the dragged one must be fully visible
                     if (ambientState.isShadeExpanded()) {
@@ -229,7 +215,7 @@
                 }
 
                 // Lets set the alpha to the one it currently has, as its currently being dragged
-                StackViewState viewState = resultState.getViewStateForView(draggedView);
+                ExpandableViewState viewState = resultState.getViewStateForView(draggedView);
                 // The dragged child should keep the set alpha
                 viewState.alpha = draggedView.getAlpha();
             }
@@ -241,8 +227,6 @@
      */
     private void initAlgorithmState(StackScrollState resultState, StackScrollAlgorithmState state,
             AmbientState ambientState) {
-        state.itemsInBottomStack = 0.0f;
-        state.partialInBottom = 0.0f;
         float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
 
         int scrollY = ambientState.getScrollY();
@@ -263,6 +247,9 @@
         for (int i = 0; i < childCount; i++) {
             ExpandableView v = (ExpandableView) hostView.getChildAt(i);
             if (v.getVisibility() != View.GONE) {
+                if (v == ambientState.getShelf()) {
+                    continue;
+                }
                 notGoneIndex = updateNotGoneIndex(resultState, state, notGoneIndex, v);
                 float increasedPadding = v.getIncreasedPaddingAmount();
                 if (increasedPadding != 0.0f) {
@@ -284,7 +271,7 @@
                     if (row.isSummaryWithChildren() && children != null) {
                         for (ExpandableNotificationRow childRow : children) {
                             if (childRow.getVisibility() != View.GONE) {
-                                StackViewState childState
+                                ExpandableViewState childState
                                         = resultState.getViewStateForView(childRow);
                                 childState.notGoneIndex = notGoneIndex;
                                 notGoneIndex++;
@@ -300,7 +287,7 @@
     private int updateNotGoneIndex(StackScrollState resultState,
             StackScrollAlgorithmState state, int notGoneIndex,
             ExpandableView v) {
-        StackViewState viewState = resultState.getViewStateForView(v);
+        ExpandableViewState viewState = resultState.getViewStateForView(v);
         viewState.notGoneIndex = notGoneIndex;
         state.visibleChildren.add(v);
         notGoneIndex++;
@@ -317,72 +304,42 @@
     private void updatePositionsForState(StackScrollState resultState,
             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
 
-        // The starting position of the bottom stack peek
-        float bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize;
-
-        // The position where the bottom stack starts.
-        float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength;
-
         // The y coordinate of the current child.
         float currentYPosition = -algorithmState.scrollY;
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             currentYPosition = updateChild(i, resultState, algorithmState, ambientState,
-                    currentYPosition, bottomStackStart);
+                    currentYPosition);
         }
     }
 
     protected float updateChild(int i, StackScrollState resultState,
             StackScrollAlgorithmState algorithmState, AmbientState ambientState,
-            float currentYPosition, float bottomStackStart) {
+            float currentYPosition) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
-        StackViewState childViewState = resultState.getViewStateForView(child);
-        childViewState.location = StackViewState.LOCATION_UNKNOWN;
+        ExpandableViewState childViewState = resultState.getViewStateForView(child);
+        childViewState.location = ExpandableViewState.LOCATION_UNKNOWN;
         int paddingAfterChild = getPaddingAfterChild(algorithmState, child);
         int childHeight = getMaxAllowedChildHeight(child);
-        int collapsedHeight = child.getCollapsedHeight();
         childViewState.yTranslation = currentYPosition;
+        boolean isDismissView = child instanceof DismissView;
         if (i == 0) {
-            updateFirstChildHeight(child, childViewState, childHeight, ambientState);
+            updateFirstChildHeight(child, childViewState, childHeight, algorithmState, ambientState);
         }
 
-        // The y position after this element
-        float nextYPosition = currentYPosition + childHeight +
-                paddingAfterChild;
-        if (nextYPosition >= bottomStackStart) {
-            // Case 1:
-            // We are in the bottom stack.
-            if (currentYPosition >= bottomStackStart) {
-                // According to the regular scroll view we are fully translated out of the
-                // bottom of the screen so we are fully in the bottom stack
-                updateStateForChildFullyInBottomStack(algorithmState,
-                        bottomStackStart, childViewState, collapsedHeight, ambientState, child);
-            } else {
-                // According to the regular scroll view we are currently translating out of /
-                // into the bottom of the screen
-                updateStateForChildTransitioningInBottom(algorithmState,
-                        bottomStackStart, child, currentYPosition,
-                        childViewState, childHeight);
-            }
+        childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
+        if (isDismissView) {
+            childViewState.yTranslation = Math.min(childViewState.yTranslation,
+                    ambientState.getInnerHeight() - childHeight);
         } else {
-            // Case 2:
-            // We are in the regular scroll area.
-            childViewState.location = StackViewState.LOCATION_MAIN_AREA;
-            clampPositionToBottomStackStart(childViewState, childViewState.height, childHeight,
-                    ambientState);
+            clampPositionToShelf(childViewState, ambientState);
         }
 
-        if (i == 0 && ambientState.getScrollY() <= 0) {
-            // The first card can get into the bottom stack if it's the only one
-            // on the lockscreen which pushes it up. Let's make sure that doesn't happen and
-            // it stays at the top
-            childViewState.yTranslation = Math.max(0, childViewState.yTranslation);
-        }
         currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
         if (currentYPosition <= 0) {
-            childViewState.location = StackViewState.LOCATION_HIDDEN_TOP;
+            childViewState.location = ExpandableViewState.LOCATION_HIDDEN_TOP;
         }
-        if (childViewState.location == StackViewState.LOCATION_UNKNOWN) {
+        if (childViewState.location == ExpandableViewState.LOCATION_UNKNOWN) {
             Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
         }
 
@@ -393,12 +350,7 @@
 
     protected int getPaddingAfterChild(StackScrollAlgorithmState algorithmState,
             ExpandableView child) {
-        Float paddingValue = algorithmState.increasedPaddingMap.get(child);
-        return paddingValue == null
-                ? mPaddingBetweenElements
-                : (int) NotificationUtils.interpolate(mPaddingBetweenElements,
-                        mIncreasedPaddingBetweenElements,
-                        paddingValue);
+        return algorithmState.getPaddingAfterChild(child);
     }
 
     private void updateHeadsUpStates(StackScrollState resultState,
@@ -414,22 +366,26 @@
             if (!row.isHeadsUp()) {
                 break;
             }
-            StackViewState childState = resultState.getViewStateForView(row);
+            ExpandableViewState childState = resultState.getViewStateForView(row);
             if (topHeadsUpEntry == null) {
                 topHeadsUpEntry = row;
-                childState.location = StackViewState.LOCATION_FIRST_HUN;
+                childState.location = ExpandableViewState.LOCATION_FIRST_HUN;
             }
             boolean isTopEntry = topHeadsUpEntry == row;
             float unmodifiedEndLocation = childState.yTranslation + childState.height;
             if (mIsExpanded) {
                 // Ensure that the heads up is always visible even when scrolled off
                 clampHunToTop(ambientState, row, childState);
-                clampHunToMaxTranslation(ambientState, row, childState);
+                if (i == 0) {
+                    // the first hun can't get off screen.
+                    clampHunToMaxTranslation(ambientState, row, childState);
+                }
             }
             if (row.isPinned()) {
                 childState.yTranslation = Math.max(childState.yTranslation, 0);
                 childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
-                StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
+                childState.hidden = false;
+                ExpandableViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
                 if (!isTopEntry && (!mIsExpanded
                         || unmodifiedEndLocation < topState.yTranslation + topState.height)) {
                     // Ensure that a headsUp doesn't vertically extend further than the heads-up at
@@ -439,11 +395,14 @@
                             - childState.height;
                 }
             }
+            if (row.isHeadsUpAnimatingAway()) {
+                childState.hidden = false;
+            }
         }
     }
 
     private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
-            StackViewState childState) {
+            ExpandableViewState childState) {
         float newTranslation = Math.max(ambientState.getTopPadding()
                 + ambientState.getStackTranslation(), childState.yTranslation);
         childState.height = (int) Math.max(childState.height - (newTranslation
@@ -452,7 +411,7 @@
     }
 
     private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
-            StackViewState childState) {
+            ExpandableViewState childState) {
         float newTranslation;
         float bottomPosition = ambientState.getMaxHeadsUpTranslation() - row.getCollapsedHeight();
         newTranslation = Math.min(childState.yTranslation, bottomPosition);
@@ -462,26 +421,23 @@
     }
 
     /**
-     * Clamp the yTranslation of the child down such that its end is at most on the beginning of
-     * the bottom stack.
+     * Clamp the height of the child down such that its end is at most on the beginning of
+     * the shelf.
      *
      * @param childViewState the view state of the child
-     * @param childHeight the height of this child
-     * @param minHeight the minumum Height of the View
+     * @param ambientState the ambient state
      */
-    private void clampPositionToBottomStackStart(StackViewState childViewState,
-            int childHeight, int minHeight, AmbientState ambientState) {
-
-        int bottomStackStart = ambientState.getInnerHeight()
-                - mBottomStackPeekSize - mBottomStackSlowDownLength;
-        int childStart = bottomStackStart - childHeight;
-        if (childStart < childViewState.yTranslation) {
-            float newHeight = bottomStackStart - childViewState.yTranslation;
-            if (newHeight < minHeight) {
-                newHeight = minHeight;
-                childViewState.yTranslation = bottomStackStart - minHeight;
-            }
-            childViewState.height = (int) newHeight;
+    private void clampPositionToShelf(ExpandableViewState childViewState,
+            AmbientState ambientState) {
+        int shelfStart = ambientState.getInnerHeight()
+                - ambientState.getShelf().getIntrinsicHeight();
+        childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
+        if (childViewState.yTranslation >= shelfStart) {
+            childViewState.hidden = true;
+            childViewState.inShelf = true;
+        }
+        if (!ambientState.isShadeExpanded()) {
+            childViewState.height = (int) (mStatusBarHeight - childViewState.yTranslation);
         }
     }
 
@@ -493,74 +449,26 @@
         return child == null? mCollapsedSize : child.getHeight();
     }
 
-    private void updateStateForChildTransitioningInBottom(StackScrollAlgorithmState algorithmState,
-            float transitioningPositionStart, ExpandableView child, float currentYPosition,
-            StackViewState childViewState, int childHeight) {
-
-        // This is the transitioning element on top of bottom stack, calculate how far we are in.
-        algorithmState.partialInBottom = 1.0f - (
-                (transitioningPositionStart - currentYPosition) / (childHeight +
-                        getPaddingAfterChild(algorithmState, child)));
-
-        // the offset starting at the transitionPosition of the bottom stack
-        float offset = mBottomStackIndentationFunctor.getValue(algorithmState.partialInBottom);
-        algorithmState.itemsInBottomStack += algorithmState.partialInBottom;
-        int newHeight = childHeight;
-        if (childHeight > child.getCollapsedHeight()) {
-            newHeight = (int) Math.max(Math.min(transitioningPositionStart + offset -
-                    getPaddingAfterChild(algorithmState, child) - currentYPosition, childHeight),
-                    child.getCollapsedHeight());
-            childViewState.height = newHeight;
-        }
-        childViewState.yTranslation = transitioningPositionStart + offset - newHeight
-                - getPaddingAfterChild(algorithmState, child);
-        childViewState.location = StackViewState.LOCATION_MAIN_AREA;
-    }
-
-    private void updateStateForChildFullyInBottomStack(StackScrollAlgorithmState algorithmState,
-            float transitioningPositionStart, StackViewState childViewState,
-            int collapsedHeight, AmbientState ambientState, ExpandableView child) {
-        float currentYPosition;
-        algorithmState.itemsInBottomStack += 1.0f;
-        if (algorithmState.itemsInBottomStack < MAX_ITEMS_IN_BOTTOM_STACK) {
-            // We are visually entering the bottom stack
-            currentYPosition = transitioningPositionStart
-                    + mBottomStackIndentationFunctor.getValue(algorithmState.itemsInBottomStack)
-                    - getPaddingAfterChild(algorithmState, child);
-            childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_PEEKING;
-        } else {
-            // we are fully inside the stack
-            if (algorithmState.itemsInBottomStack > MAX_ITEMS_IN_BOTTOM_STACK + 2) {
-                childViewState.hidden = true;
-                childViewState.shadowAlpha = 0.0f;
-            } else if (algorithmState.itemsInBottomStack
-                    > MAX_ITEMS_IN_BOTTOM_STACK + 1) {
-                childViewState.shadowAlpha = 1.0f - algorithmState.partialInBottom;
-            }
-            childViewState.location = StackViewState.LOCATION_BOTTOM_STACK_HIDDEN;
-            currentYPosition = ambientState.getInnerHeight();
-        }
-        childViewState.height = collapsedHeight;
-        childViewState.yTranslation = currentYPosition - collapsedHeight;
-    }
-
-
     /**
      * Update the height of the first child i.e clamp it to the bottom stack
-     *
      * @param child the child to update
      * @param childViewState the viewstate of the child
      * @param childHeight the height of the child
+     * @param algorithmState the algorithm state
      * @param ambientState The ambient state of the algorithm
      */
-    protected void updateFirstChildHeight(ExpandableView child, StackViewState childViewState,
-                                          int childHeight, AmbientState ambientState) {
+    protected void updateFirstChildHeight(ExpandableView child, ExpandableViewState childViewState,
+            int childHeight, StackScrollAlgorithmState algorithmState,
+            AmbientState ambientState) {
 
-            // The starting position of the bottom stack peek
-            int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
-                    mBottomStackSlowDownLength + ambientState.getScrollY();
+        int bottomStart= ambientState.getInnerHeight();
+        if (algorithmState.visibleChildren.size() > 1) {
+            bottomStart -= ambientState.getShelf().getIntrinsicHeight()
+                    - mPaddingBetweenElements;
+        }
+        bottomStart += ambientState.getScrollY();
             // Collapse and expand the first child while the shade is being expanded
-        childViewState.height = (int) Math.max(Math.min(bottomPeekStart, (float) childHeight),
+        childViewState.height = (int) Math.max(Math.min(bottomStart, (float) childHeight),
                     child.getCollapsedHeight());
     }
 
@@ -576,37 +484,19 @@
         int childCount = algorithmState.visibleChildren.size();
         float childrenOnTop = 0.0f;
         for (int i = childCount - 1; i >= 0; i--) {
-            updateChildZValue(i, childCount, childrenOnTop,
+            updateChildZValue(i, childrenOnTop,
                     resultState, algorithmState, ambientState);
         }
     }
 
-    protected void updateChildZValue(int i, int childCount, float childrenOnTop,
+    protected void updateChildZValue(int i, float childrenOnTop,
             StackScrollState resultState, StackScrollAlgorithmState algorithmState,
             AmbientState ambientState) {
         ExpandableView child = algorithmState.visibleChildren.get(i);
-        StackViewState childViewState = resultState.getViewStateForView(child);
-        if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
-            // We are in the bottom stack
-            float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack);
-            float zSubtraction;
-            if (numItemsAbove <= 1.0f) {
-                float factor = 0.2f;
-                // Lets fade in slower to the threshold to make the shadow fade in look nicer
-                if (numItemsAbove <= factor) {
-                    zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD
-                            * numItemsAbove * (1.0f / factor);
-                } else {
-                    zSubtraction = FakeShadowView.SHADOW_SIBLING_TRESHOLD
-                            + (numItemsAbove - factor) * (1.0f / (1.0f - factor))
-                            * (mZDistanceBetweenElements
-                            - FakeShadowView.SHADOW_SIBLING_TRESHOLD);
-                }
-            } else {
-                zSubtraction = numItemsAbove * mZDistanceBetweenElements;
-            }
-            childViewState.zTranslation = mZBasicHeight - zSubtraction;
-        } else if (child.mustStayOnScreen()
+        ExpandableViewState childViewState = resultState.getViewStateForView(child);
+        int zDistanceBetweenElements = ambientState.getZDistanceBetweenElements();
+        float baseZ = ambientState.getBaseZHeight();
+        if (child.mustStayOnScreen()
                 && childViewState.yTranslation < ambientState.getTopPadding()
                 + ambientState.getStackTranslation()) {
             if (childrenOnTop != 0.0f) {
@@ -616,37 +506,34 @@
                         + ambientState.getStackTranslation() - childViewState.yTranslation;
                 childrenOnTop += Math.min(1.0f, overlap / childViewState.height);
             }
-            childViewState.zTranslation = mZBasicHeight
-                    + childrenOnTop * mZDistanceBetweenElements;
-        } else {
-            childViewState.zTranslation = mZBasicHeight;
-        }
-    }
-
-    private boolean isMaxSizeInitialized(ExpandableView child) {
-        if (child instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            return row.isMaxExpandHeightInitialized();
-        }
-        return child == null || child.getWidth() != 0;
-    }
-
-    private View findFirstVisibleChild(ViewGroup container) {
-        int childCount = container.getChildCount();
-        for (int i = 0; i < childCount; i++) {
-            View child = container.getChildAt(i);
-            if (child.getVisibility() != View.GONE) {
-                return child;
+            childViewState.zTranslation = baseZ
+                    + childrenOnTop * zDistanceBetweenElements;
+        } else if (i == 0 && child.isAboveShelf()) {
+            // In case this is a new view that has never been measured before, we don't want to
+            // elevate if we are currently expanded more then the notification
+            int shelfHeight = ambientState.getShelf().getIntrinsicHeight();
+            float shelfStart = ambientState.getInnerHeight()
+                    - shelfHeight + ambientState.getTopPadding()
+                    + ambientState.getStackTranslation();
+            float notificationEnd = childViewState.yTranslation + child.getPinnedHeadsUpHeight()
+                    + mPaddingBetweenElements;
+            if (shelfStart > notificationEnd) {
+                childViewState.zTranslation = baseZ;
+            } else {
+                float factor = (notificationEnd - shelfStart) / shelfHeight;
+                factor = Math.min(factor, 1.0f);
+                childViewState.zTranslation = baseZ + factor * zDistanceBetweenElements;
             }
+        } else {
+            childViewState.zTranslation = baseZ;
         }
-        return null;
     }
 
     public void setIsExpanded(boolean isExpanded) {
         this.mIsExpanded = isExpanded;
     }
 
-    protected class StackScrollAlgorithmState {
+    public class StackScrollAlgorithmState {
 
         /**
          * The scroll position of the algorithm
@@ -654,16 +541,6 @@
         public int scrollY;
 
         /**
-         * The quantity of items which are in the bottom stack.
-         */
-        public float itemsInBottomStack;
-
-        /**
-         * how far in is the element currently transitioning into the bottom stack
-         */
-        public float partialInBottom;
-
-        /**
          * The children from the host view which are not gone.
          */
         public final ArrayList<ExpandableView> visibleChildren = new ArrayList<ExpandableView>();
@@ -673,6 +550,15 @@
          * no increased padding, a value of 1 means full padding.
          */
         public final HashMap<ExpandableView, Float> increasedPaddingMap = new HashMap<>();
+
+        public int getPaddingAfterChild(ExpandableView child) {
+            Float paddingValue = increasedPaddingMap.get(child);
+            return paddingValue == null
+                    ? mPaddingBetweenElements
+                    : (int) NotificationUtils.interpolate(mPaddingBetweenElements,
+                            mIncreasedPaddingBetweenElements,
+                            paddingValue);
+        }
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
index 8f0cd8e..e3c746b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollState.java
@@ -21,8 +21,6 @@
 import android.view.ViewGroup;
 
 import com.android.systemui.R;
-import com.android.systemui.statusbar.DismissView;
-import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
 
@@ -38,14 +36,11 @@
     private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild";
 
     private final ViewGroup mHostView;
-    private WeakHashMap<ExpandableView, StackViewState> mStateMap;
-    private final int mClearAllTopPadding;
+    private WeakHashMap<ExpandableView, ExpandableViewState> mStateMap;
 
     public StackScrollState(ViewGroup hostView) {
         mHostView = hostView;
         mStateMap = new WeakHashMap<>();
-        mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize(
-                R.dimen.clear_all_padding_top);
     }
 
     public ViewGroup getHostView() {
@@ -73,9 +68,9 @@
     }
 
     private void resetViewState(ExpandableView view) {
-        StackViewState viewState = mStateMap.get(view);
+        ExpandableViewState viewState = mStateMap.get(view);
         if (viewState == null) {
-            viewState = new StackViewState();
+            viewState = view.createNewViewState(this);
             mStateMap.put(view, viewState);
         }
         // initialize with the default values of the view
@@ -84,10 +79,14 @@
         viewState.alpha = 1f;
         viewState.shadowAlpha = 1f;
         viewState.notGoneIndex = -1;
+        viewState.xTranslation = view.getTranslationX();
         viewState.hidden = false;
+        viewState.scaleX = view.getScaleX();
+        viewState.scaleY = view.getScaleY();
+        viewState.inShelf = false;
     }
 
-    public StackViewState getViewStateForView(View requestedView) {
+    public ExpandableViewState getViewStateForView(View requestedView) {
         return mStateMap.get(requestedView);
     }
 
@@ -103,130 +102,16 @@
         int numChildren = mHostView.getChildCount();
         for (int i = 0; i < numChildren; i++) {
             ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
-            StackViewState state = mStateMap.get(child);
-            if (!applyState(child, state)) {
+            ExpandableViewState state = mStateMap.get(child);
+            if (state == null) {
+                Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
+                        "to the hostView");
                 continue;
             }
-            if (child instanceof DismissView) {
-                DismissView dismissView = (DismissView) child;
-                boolean visible = state.clipTopAmount < mClearAllTopPadding;
-                dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone());
-            } else if (child instanceof EmptyShadeView) {
-                EmptyShadeView emptyShadeView = (EmptyShadeView) child;
-                boolean visible = state.clipTopAmount <= 0;
-                emptyShadeView.performVisibilityAnimation(
-                        visible && !emptyShadeView.willBeGone());
+            if (state.gone) {
+                continue;
             }
-        }
-    }
-
-    /**
-     * Applies a  {@link StackViewState} to an  {@link ExpandableView}.
-     *
-     * @return whether the state was applied correctly
-     */
-    public boolean applyState(ExpandableView view, StackViewState state) {
-        if (state == null) {
-            Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
-                    "to the hostView");
-            return false;
-        }
-        if (state.gone) {
-            return false;
-        }
-        applyViewState(view, state);
-
-        int height = view.getActualHeight();
-        int newHeight = state.height;
-
-        // apply height
-        if (height != newHeight) {
-            view.setActualHeight(newHeight, false /* notifyListeners */);
-        }
-
-        float shadowAlpha = view.getShadowAlpha();
-        float newShadowAlpha = state.shadowAlpha;
-
-        // apply shadowAlpha
-        if (shadowAlpha != newShadowAlpha) {
-            view.setShadowAlpha(newShadowAlpha);
-        }
-
-        // apply dimming
-        view.setDimmed(state.dimmed, false /* animate */);
-
-        // apply hiding sensitive
-        view.setHideSensitive(
-                state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
-
-        // apply speed bump state
-        view.setBelowSpeedBump(state.belowSpeedBump);
-
-        // apply dark
-        view.setDark(state.dark, false /* animate */, 0 /* delay */);
-
-        // apply clipping
-        float oldClipTopAmount = view.getClipTopAmount();
-        if (oldClipTopAmount != state.clipTopAmount) {
-            view.setClipTopAmount(state.clipTopAmount);
-        }
-        if (view instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
-            if (state.isBottomClipped) {
-                row.setClipToActualHeight(true);
-            }
-            row.applyChildrenState(this);
-        }
-        return true;
-    }
-
-    /**
-     * Applies a  {@link ViewState} to a normal view.
-     */
-    public void applyViewState(View view, ViewState state) {
-        float alpha = view.getAlpha();
-        float yTranslation = view.getTranslationY();
-        float xTranslation = view.getTranslationX();
-        float zTranslation = view.getTranslationZ();
-        float newAlpha = state.alpha;
-        float newYTranslation = state.yTranslation;
-        float newZTranslation = state.zTranslation;
-        boolean becomesInvisible = newAlpha == 0.0f || state.hidden;
-        if (alpha != newAlpha && xTranslation == 0) {
-            // apply layer type
-            boolean becomesFullyVisible = newAlpha == 1.0f;
-            boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
-                    && view.hasOverlappingRendering();
-            int layerType = view.getLayerType();
-            int newLayerType = newLayerTypeIsHardware
-                    ? View.LAYER_TYPE_HARDWARE
-                    : View.LAYER_TYPE_NONE;
-            if (layerType != newLayerType) {
-                view.setLayerType(newLayerType, null);
-            }
-
-            // apply alpha
-            view.setAlpha(newAlpha);
-        }
-
-        // apply visibility
-        int oldVisibility = view.getVisibility();
-        int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
-        if (newVisibility != oldVisibility) {
-            if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
-                // We don't want views to change visibility when they are animating to GONE
-                view.setVisibility(newVisibility);
-            }
-        }
-
-        // apply yTranslation
-        if (yTranslation != newYTranslation) {
-            view.setTranslationY(newYTranslation);
-        }
-
-        // apply zTranslation
-        if (zTranslation != newZTranslation) {
-            view.setTranslationZ(newZTranslation);
+            state.applyToView(child);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
index 3804b42..1f29b4f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackStateAnimator.java
@@ -18,9 +18,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
+import android.util.Property;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.Interpolator;
@@ -29,7 +28,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.NotificationShelf;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -54,28 +53,10 @@
     public static final int DELAY_EFFECT_MAX_INDEX_DIFFERENCE = 2;
     public static final int ANIMATION_DELAY_HEADS_UP = 120;
 
-    private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
-    private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
-    private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
-    private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
-    private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
-    private static final int TAG_ANIMATOR_SHADOW_ALPHA = R.id.shadow_alpha_animator_tag;
-    private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
-    private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
-    private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
-    private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
-    private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
-    private static final int TAG_END_SHADOW_ALPHA = R.id.shadow_alpha_animator_end_value_tag;
-    private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
-    private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
-    private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
-    private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
-    private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
-    private static final int TAG_START_SHADOW_ALPHA = R.id.shadow_alpha_animator_start_value_tag;
-
     private final Interpolator mHeadsUpAppearInterpolator;
     private final int mGoToFullShadeAppearingTranslation;
-    private final StackViewState mTmpState = new StackViewState();
+    private final ExpandableViewState mTmpState = new ExpandableViewState();
+    private final AnimationProperties mAnimationProperties;
     public NotificationStackScrollLayout mHostLayout;
     private ArrayList<NotificationStackScrollLayout.AnimationEvent> mNewEvents =
             new ArrayList<>();
@@ -95,6 +76,7 @@
     private int mHeadsUpAppearHeightBottom;
     private boolean mShadeExpanded;
     private ArrayList<View> mChildrenToClearFromOverlay = new ArrayList<>();
+    private NotificationShelf mShelf;
 
     public StackStateAnimator(NotificationStackScrollLayout hostLayout) {
         mHostLayout = hostLayout;
@@ -102,6 +84,30 @@
                 hostLayout.getContext().getResources().getDimensionPixelSize(
                         R.dimen.go_to_full_shade_appearing_translation);
         mHeadsUpAppearInterpolator = new HeadsUpAppearInterpolator();
+        mAnimationProperties = new AnimationProperties() {
+            @Override
+            public AnimationFilter getAnimationFilter() {
+                return mAnimationFilter;
+            }
+
+            @Override
+            public AnimatorListenerAdapter getAnimationFinishListener() {
+                return getGlobalAnimationFinishedListener();
+            }
+
+            @Override
+            public boolean wasAdded(View view) {
+                return mNewAddChildren.contains(view);
+            }
+
+            @Override
+            public Interpolator getCustomInterpolator(View child, Property property) {
+                if (mHeadsUpAppearChildren.contains(child) && View.TRANSLATION_Y.equals(property)) {
+                    return mHeadsUpAppearInterpolator;
+                }
+                return null;
+            }
+        };
     }
 
     public boolean isRunning() {
@@ -122,13 +128,14 @@
         for (int i = 0; i < childCount; i++) {
             final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
 
-            StackViewState viewState = finalState.getViewStateForView(child);
+            ExpandableViewState viewState = finalState.getViewStateForView(child);
             if (viewState == null || child.getVisibility() == View.GONE
                     || applyWithoutAnimation(child, viewState, finalState)) {
                 continue;
             }
 
-            startStackAnimations(child, viewState, finalState, i, -1 /* fixedDelay */);
+            initAnimationProperties(finalState, child, viewState);
+            viewState.animateTo(child, mAnimationProperties);
         }
         if (!isRunning()) {
             // no child has preformed any animation, lets finish
@@ -140,17 +147,47 @@
         mNewAddChildren.clear();
     }
 
+    private void initAnimationProperties(StackScrollState finalState, ExpandableView child,
+            ExpandableViewState viewState) {
+        boolean wasAdded = mAnimationProperties.wasAdded(child);
+        mAnimationProperties.duration = mCurrentLength;
+        adaptDurationWhenGoingToFullShade(child, viewState, wasAdded);
+        mAnimationProperties.delay = 0;
+        if (wasAdded || mAnimationFilter.hasDelays
+                        && (viewState.yTranslation != child.getTranslationY()
+                        || viewState.zTranslation != child.getTranslationZ()
+                        || viewState.alpha != child.getAlpha()
+                        || viewState.height != child.getActualHeight()
+                        || viewState.clipTopAmount != child.getClipTopAmount()
+                        || viewState.dark != child.isDark()
+                        || viewState.shadowAlpha != child.getShadowAlpha())) {
+            mAnimationProperties.delay = mCurrentAdditionalDelay
+                    + calculateChildAnimationDelay(viewState, finalState);
+        }
+    }
+
+    private void adaptDurationWhenGoingToFullShade(ExpandableView child,
+            ExpandableViewState viewState, boolean wasAdded) {
+        if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
+            child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
+            float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
+            longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
+            mAnimationProperties.duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
+                    (long) (100 * longerDurationFactor);
+        }
+    }
+
     /**
      * Determines if a view should not perform an animation and applies it directly.
      *
      * @return true if no animation should be performed
      */
-    private boolean applyWithoutAnimation(ExpandableView child, StackViewState viewState,
+    private boolean applyWithoutAnimation(ExpandableView child, ExpandableViewState viewState,
             StackScrollState finalState) {
         if (mShadeExpanded) {
             return false;
         }
-        if (getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null) {
+        if (ViewState.isAnimatingY(child)) {
             // A Y translation animation is running
             return false;
         }
@@ -162,7 +199,7 @@
             // This is another headsUp which might move. Let's animate!
             return false;
         }
-        finalState.applyState(child, viewState);
+        viewState.applyToView(child);
         return true;
     }
 
@@ -171,7 +208,7 @@
         for (int i = childCount - 1; i >= 0; i--) {
             final ExpandableView child = (ExpandableView) mHostLayout.getChildAt(i);
 
-            StackViewState viewState = finalState.getViewStateForView(child);
+            ExpandableViewState viewState = finalState.getViewStateForView(child);
             if (viewState == null || child.getVisibility() == View.GONE) {
                 continue;
             }
@@ -182,144 +219,7 @@
         return -1;
     }
 
-
-    /**
-     * Start an animation to the given  {@link StackViewState}.
-     *
-     * @param child the child to start the animation on
-     * @param viewState the {@link StackViewState} of the view to animate to
-     * @param finalState the final state after the animation
-     * @param i the index of the view; only relevant if the view is the speed bump and is
-     *          ignored otherwise
-     * @param fixedDelay a fixed delay if desired or -1 if the delay should be calculated
-     */
-    public void startStackAnimations(final ExpandableView child, StackViewState viewState,
-            StackScrollState finalState, int i, long fixedDelay) {
-        boolean wasAdded = mNewAddChildren.contains(child);
-        long duration = mCurrentLength;
-        if (wasAdded && mAnimationFilter.hasGoToFullShadeEvent) {
-            child.setTranslationY(child.getTranslationY() + mGoToFullShadeAppearingTranslation);
-            float longerDurationFactor = viewState.notGoneIndex - mCurrentLastNotAddedIndex;
-            longerDurationFactor = (float) Math.pow(longerDurationFactor, 0.7f);
-            duration = ANIMATION_DURATION_APPEAR_DISAPPEAR + 50 +
-                    (long) (100 * longerDurationFactor);
-        }
-        boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
-        boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
-        boolean alphaChanging = viewState.alpha != child.getAlpha();
-        boolean heightChanging = viewState.height != child.getActualHeight();
-        boolean shadowAlphaChanging = viewState.shadowAlpha != child.getShadowAlpha();
-        boolean darkChanging = viewState.dark != child.isDark();
-        boolean topInsetChanging = viewState.clipTopAmount != child.getClipTopAmount();
-        boolean hasDelays = mAnimationFilter.hasDelays;
-        boolean isDelayRelevant = yTranslationChanging || zTranslationChanging || alphaChanging
-                || heightChanging || topInsetChanging || darkChanging || shadowAlphaChanging;
-        long delay = 0;
-        if (fixedDelay != -1) {
-            delay = fixedDelay;
-        } else if (hasDelays && isDelayRelevant || wasAdded) {
-            delay = mCurrentAdditionalDelay + calculateChildAnimationDelay(viewState, finalState);
-        }
-
-        startViewAnimations(child, viewState, delay, duration);
-
-        // start height animation
-        if (heightChanging) {
-            startHeightAnimation(child, viewState, duration, delay);
-        }  else {
-            abortAnimation(child, TAG_ANIMATOR_HEIGHT);
-        }
-
-        // start shadow alpha animation
-        if (shadowAlphaChanging) {
-            startShadowAlphaAnimation(child, viewState, duration, delay);
-        } else {
-            abortAnimation(child, TAG_ANIMATOR_SHADOW_ALPHA);
-        }
-
-        // start top inset animation
-        if (topInsetChanging) {
-            startInsetAnimation(child, viewState, duration, delay);
-        } else {
-            abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
-        }
-
-        // start dimmed animation
-        child.setDimmed(viewState.dimmed, mAnimationFilter.animateDimmed);
-
-        // apply speed bump state
-        child.setBelowSpeedBump(viewState.belowSpeedBump);
-
-        // start hiding sensitive animation
-        child.setHideSensitive(viewState.hideSensitive, mAnimationFilter.animateHideSensitive,
-                delay, duration);
-
-        // start dark animation
-        child.setDark(viewState.dark, mAnimationFilter.animateDark, delay);
-
-        if (wasAdded) {
-            child.performAddAnimation(delay, mCurrentLength);
-        }
-        if (child instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
-            row.startChildAnimation(finalState, this, delay, duration);
-        }
-    }
-
-    /**
-     * Start an animation to a new {@link ViewState}.
-     *
-     * @param child the child to start the animation on
-     * @param viewState the  {@link StackViewState} of the view to animate to
-     * @param delay a fixed delay
-     * @param duration the duration of the animation
-     */
-    public void startViewAnimations(View child, ViewState viewState, long delay, long duration) {
-        boolean wasVisible = child.getVisibility() == View.VISIBLE;
-        final float alpha = viewState.alpha;
-        if (!wasVisible && (alpha != 0 || child.getAlpha() != 0)
-                && !viewState.gone && !viewState.hidden) {
-            child.setVisibility(View.VISIBLE);
-        }
-        boolean yTranslationChanging = child.getTranslationY() != viewState.yTranslation;
-        boolean zTranslationChanging = child.getTranslationZ() != viewState.zTranslation;
-        float childAlpha = child.getAlpha();
-        boolean alphaChanging = viewState.alpha != childAlpha;
-        if (child instanceof ExpandableView) {
-            // We don't want views to change visibility when they are animating to GONE
-            alphaChanging &= !((ExpandableView) child).willBeGone();
-        }
-
-        // start translationY animation
-        if (yTranslationChanging) {
-            startYTranslationAnimation(child, viewState, duration, delay);
-        } else {
-            abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y);
-        }
-
-        // start translationZ animation
-        if (zTranslationChanging) {
-            startZTranslationAnimation(child, viewState, duration, delay);
-        } else {
-            abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z);
-        }
-
-        // start alpha animation
-        if (alphaChanging && child.getTranslationX() == 0) {
-            startAlphaAnimation(child, viewState, duration, delay);
-        }  else {
-            abortAnimation(child, TAG_ANIMATOR_ALPHA);
-        }
-    }
-
-    private void abortAnimation(View child, int animatorTag) {
-        Animator previousAnimator = getChildTag(child, animatorTag);
-        if (previousAnimator != null) {
-            previousAnimator.cancel();
-        }
-    }
-
-    private long calculateChildAnimationDelay(StackViewState viewState,
+    private long calculateChildAnimationDelay(ExpandableViewState viewState,
             StackScrollState finalState) {
         if (mAnimationFilter.hasDarkEvent) {
             return calculateDelayDark(viewState);
@@ -374,7 +274,7 @@
         return minDelay;
     }
 
-    private long calculateDelayDark(StackViewState viewState) {
+    private long calculateDelayDark(ExpandableViewState viewState) {
         int referenceIndex;
         if (mAnimationFilter.darkAnimationOriginIndex ==
                 NotificationStackScrollLayout.AnimationEvent.DARK_ANIMATION_ORIGIN_INDEX_ABOVE) {
@@ -388,400 +288,19 @@
         return Math.abs(referenceIndex - viewState.notGoneIndex) * ANIMATION_DELAY_PER_ELEMENT_DARK;
     }
 
-    private long calculateDelayGoToFullShade(StackViewState viewState) {
+    private long calculateDelayGoToFullShade(ExpandableViewState viewState) {
+        int shelfIndex = mShelf.getNotGoneIndex();
         float index = viewState.notGoneIndex;
+        long result = 0;
+        if (index > shelfIndex) {
+            float diff = index - shelfIndex;
+            diff = (float) Math.pow(diff, 0.7f);
+            result += (long) (diff * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE * 0.25);
+            index = shelfIndex;
+        }
         index = (float) Math.pow(index, 0.7f);
-        return (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
-    }
-
-    private void startShadowAlphaAnimation(final ExpandableView child,
-            StackViewState viewState, long duration, long delay) {
-        Float previousStartValue = getChildTag(child, TAG_START_SHADOW_ALPHA);
-        Float previousEndValue = getChildTag(child, TAG_END_SHADOW_ALPHA);
-        float newEndValue = viewState.shadowAlpha;
-        if (previousEndValue != null && previousEndValue == newEndValue) {
-            return;
-        }
-        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SHADOW_ALPHA);
-        if (!mAnimationFilter.animateShadowAlpha) {
-            // just a local update was performed
-            if (previousAnimator != null) {
-                // we need to increase all animation keyframes of the previous animator by the
-                // relative change to the end value
-                PropertyValuesHolder[] values = previousAnimator.getValues();
-                float relativeDiff = newEndValue - previousEndValue;
-                float newStartValue = previousStartValue + relativeDiff;
-                values[0].setFloatValues(newStartValue, newEndValue);
-                child.setTag(TAG_START_SHADOW_ALPHA, newStartValue);
-                child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
-                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
-                return;
-            } else {
-                // no new animation needed, let's just apply the value
-                child.setShadowAlpha(newEndValue);
-                return;
-            }
-        }
-
-        ValueAnimator animator = ValueAnimator.ofFloat(child.getShadowAlpha(), newEndValue);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                child.setShadowAlpha((float) animation.getAnimatedValue());
-            }
-        });
-        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
-        animator.setDuration(newDuration);
-        if (delay > 0 && (previousAnimator == null
-                || previousAnimator.getAnimatedFraction() == 0)) {
-            animator.setStartDelay(delay);
-        }
-        animator.addListener(getGlobalAnimationFinishedListener());
-        // remove the tag when the animation is finished
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, null);
-                child.setTag(TAG_START_SHADOW_ALPHA, null);
-                child.setTag(TAG_END_SHADOW_ALPHA, null);
-            }
-        });
-        startAnimator(animator);
-        child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, animator);
-        child.setTag(TAG_START_SHADOW_ALPHA, child.getShadowAlpha());
-        child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
-    }
-
-    private void startHeightAnimation(final ExpandableView child,
-            StackViewState viewState, long duration, long delay) {
-        Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
-        Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
-        int newEndValue = viewState.height;
-        if (previousEndValue != null && previousEndValue == newEndValue) {
-            return;
-        }
-        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
-        if (!mAnimationFilter.animateHeight) {
-            // just a local update was performed
-            if (previousAnimator != null) {
-                // we need to increase all animation keyframes of the previous animator by the
-                // relative change to the end value
-                PropertyValuesHolder[] values = previousAnimator.getValues();
-                int relativeDiff = newEndValue - previousEndValue;
-                int newStartValue = previousStartValue + relativeDiff;
-                values[0].setIntValues(newStartValue, newEndValue);
-                child.setTag(TAG_START_HEIGHT, newStartValue);
-                child.setTag(TAG_END_HEIGHT, newEndValue);
-                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
-                return;
-            } else {
-                // no new animation needed, let's just apply the value
-                child.setActualHeight(newEndValue, false);
-                return;
-            }
-        }
-
-        ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                child.setActualHeight((int) animation.getAnimatedValue(),
-                        false /* notifyListeners */);
-            }
-        });
-        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
-        animator.setDuration(newDuration);
-        if (delay > 0 && (previousAnimator == null
-                || previousAnimator.getAnimatedFraction() == 0)) {
-            animator.setStartDelay(delay);
-        }
-        animator.addListener(getGlobalAnimationFinishedListener());
-        // remove the tag when the animation is finished
-        animator.addListener(new AnimatorListenerAdapter() {
-            boolean mWasCancelled;
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                child.setTag(TAG_ANIMATOR_HEIGHT, null);
-                child.setTag(TAG_START_HEIGHT, null);
-                child.setTag(TAG_END_HEIGHT, null);
-                child.setActualHeightAnimating(false);
-                if (!mWasCancelled && child instanceof ExpandableNotificationRow) {
-                    ((ExpandableNotificationRow) child).setGroupExpansionChanging(
-                            false /* isExpansionChanging */);
-                }
-            }
-
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mWasCancelled = false;
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mWasCancelled = true;
-            }
-        });
-        startAnimator(animator);
-        child.setTag(TAG_ANIMATOR_HEIGHT, animator);
-        child.setTag(TAG_START_HEIGHT, child.getActualHeight());
-        child.setTag(TAG_END_HEIGHT, newEndValue);
-        child.setActualHeightAnimating(true);
-    }
-
-    private void startInsetAnimation(final ExpandableView child,
-            StackViewState viewState, long duration, long delay) {
-        Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
-        Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
-        int newEndValue = viewState.clipTopAmount;
-        if (previousEndValue != null && previousEndValue == newEndValue) {
-            return;
-        }
-        ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
-        if (!mAnimationFilter.animateTopInset) {
-            // just a local update was performed
-            if (previousAnimator != null) {
-                // we need to increase all animation keyframes of the previous animator by the
-                // relative change to the end value
-                PropertyValuesHolder[] values = previousAnimator.getValues();
-                int relativeDiff = newEndValue - previousEndValue;
-                int newStartValue = previousStartValue + relativeDiff;
-                values[0].setIntValues(newStartValue, newEndValue);
-                child.setTag(TAG_START_TOP_INSET, newStartValue);
-                child.setTag(TAG_END_TOP_INSET, newEndValue);
-                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
-                return;
-            } else {
-                // no new animation needed, let's just apply the value
-                child.setClipTopAmount(newEndValue);
-                return;
-            }
-        }
-
-        ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
-        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                child.setClipTopAmount((int) animation.getAnimatedValue());
-            }
-        });
-        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
-        animator.setDuration(newDuration);
-        if (delay > 0 && (previousAnimator == null
-                || previousAnimator.getAnimatedFraction() == 0)) {
-            animator.setStartDelay(delay);
-        }
-        animator.addListener(getGlobalAnimationFinishedListener());
-        // remove the tag when the animation is finished
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                child.setTag(TAG_ANIMATOR_TOP_INSET, null);
-                child.setTag(TAG_START_TOP_INSET, null);
-                child.setTag(TAG_END_TOP_INSET, null);
-            }
-        });
-        startAnimator(animator);
-        child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
-        child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
-        child.setTag(TAG_END_TOP_INSET, newEndValue);
-    }
-
-    private void startAlphaAnimation(final View child,
-            final ViewState viewState, long duration, long delay) {
-        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
-        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
-        final float newEndValue = viewState.alpha;
-        if (previousEndValue != null && previousEndValue == newEndValue) {
-            return;
-        }
-        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
-        if (!mAnimationFilter.animateAlpha) {
-            // just a local update was performed
-            if (previousAnimator != null) {
-                // we need to increase all animation keyframes of the previous animator by the
-                // relative change to the end value
-                PropertyValuesHolder[] values = previousAnimator.getValues();
-                float relativeDiff = newEndValue - previousEndValue;
-                float newStartValue = previousStartValue + relativeDiff;
-                values[0].setFloatValues(newStartValue, newEndValue);
-                child.setTag(TAG_START_ALPHA, newStartValue);
-                child.setTag(TAG_END_ALPHA, newEndValue);
-                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
-                return;
-            } else {
-                // no new animation needed, let's just apply the value
-                child.setAlpha(newEndValue);
-                if (newEndValue == 0) {
-                    child.setVisibility(View.INVISIBLE);
-                }
-            }
-        }
-
-        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
-                child.getAlpha(), newEndValue);
-        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        // Handle layer type
-        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-        animator.addListener(new AnimatorListenerAdapter() {
-            public boolean mWasCancelled;
-
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                child.setLayerType(View.LAYER_TYPE_NONE, null);
-                if (newEndValue == 0 && !mWasCancelled) {
-                    child.setVisibility(View.INVISIBLE);
-                }
-                // remove the tag when the animation is finished
-                child.setTag(TAG_ANIMATOR_ALPHA, null);
-                child.setTag(TAG_START_ALPHA, null);
-                child.setTag(TAG_END_ALPHA, null);
-            }
-
-            @Override
-            public void onAnimationCancel(Animator animation) {
-                mWasCancelled = true;
-            }
-
-            @Override
-            public void onAnimationStart(Animator animation) {
-                mWasCancelled = false;
-            }
-        });
-        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
-        animator.setDuration(newDuration);
-        if (delay > 0 && (previousAnimator == null
-                || previousAnimator.getAnimatedFraction() == 0)) {
-            animator.setStartDelay(delay);
-        }
-        animator.addListener(getGlobalAnimationFinishedListener());
-
-        startAnimator(animator);
-        child.setTag(TAG_ANIMATOR_ALPHA, animator);
-        child.setTag(TAG_START_ALPHA, child.getAlpha());
-        child.setTag(TAG_END_ALPHA, newEndValue);
-    }
-
-    private void startZTranslationAnimation(final View child,
-            final ViewState viewState, long duration, long delay) {
-        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
-        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
-        float newEndValue = viewState.zTranslation;
-        if (previousEndValue != null && previousEndValue == newEndValue) {
-            return;
-        }
-        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
-        if (!mAnimationFilter.animateZ) {
-            // just a local update was performed
-            if (previousAnimator != null) {
-                // we need to increase all animation keyframes of the previous animator by the
-                // relative change to the end value
-                PropertyValuesHolder[] values = previousAnimator.getValues();
-                float relativeDiff = newEndValue - previousEndValue;
-                float newStartValue = previousStartValue + relativeDiff;
-                values[0].setFloatValues(newStartValue, newEndValue);
-                child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
-                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
-                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
-                return;
-            } else {
-                // no new animation needed, let's just apply the value
-                child.setTranslationZ(newEndValue);
-            }
-        }
-
-        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
-                child.getTranslationZ(), newEndValue);
-        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
-        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
-        animator.setDuration(newDuration);
-        if (delay > 0 && (previousAnimator == null
-                || previousAnimator.getAnimatedFraction() == 0)) {
-            animator.setStartDelay(delay);
-        }
-        animator.addListener(getGlobalAnimationFinishedListener());
-        // remove the tag when the animation is finished
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
-                child.setTag(TAG_START_TRANSLATION_Z, null);
-                child.setTag(TAG_END_TRANSLATION_Z, null);
-            }
-        });
-        startAnimator(animator);
-        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
-        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
-        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
-    }
-
-    private void startYTranslationAnimation(final View child,
-            ViewState viewState, long duration, long delay) {
-        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
-        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
-        float newEndValue = viewState.yTranslation;
-        if (previousEndValue != null && previousEndValue == newEndValue) {
-            return;
-        }
-        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
-        if (!mAnimationFilter.animateY) {
-            // just a local update was performed
-            if (previousAnimator != null) {
-                // we need to increase all animation keyframes of the previous animator by the
-                // relative change to the end value
-                PropertyValuesHolder[] values = previousAnimator.getValues();
-                float relativeDiff = newEndValue - previousEndValue;
-                float newStartValue = previousStartValue + relativeDiff;
-                values[0].setFloatValues(newStartValue, newEndValue);
-                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
-                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
-                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
-                return;
-            } else {
-                // no new animation needed, let's just apply the value
-                child.setTranslationY(newEndValue);
-                return;
-            }
-        }
-
-        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
-                child.getTranslationY(), newEndValue);
-        Interpolator interpolator = mHeadsUpAppearChildren.contains(child) ?
-                mHeadsUpAppearInterpolator :Interpolators.FAST_OUT_SLOW_IN;
-        animator.setInterpolator(interpolator);
-        long newDuration = cancelAnimatorAndGetNewDuration(duration, previousAnimator);
-        animator.setDuration(newDuration);
-        if (delay > 0 && (previousAnimator == null
-                || previousAnimator.getAnimatedFraction() == 0)) {
-            animator.setStartDelay(delay);
-        }
-        animator.addListener(getGlobalAnimationFinishedListener());
-        final boolean isHeadsUpDisappear = mHeadsUpDisappearChildren.contains(child);
-        // remove the tag when the animation is finished
-        animator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                HeadsUpManager.setIsClickedNotification(child, false);
-                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
-                child.setTag(TAG_START_TRANSLATION_Y, null);
-                child.setTag(TAG_END_TRANSLATION_Y, null);
-                if (isHeadsUpDisappear) {
-                    ((ExpandableNotificationRow) child).setHeadsupDisappearRunning(false);
-                }
-            }
-        });
-        startAnimator(animator);
-        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
-        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
-        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
-    }
-
-    private void startAnimator(ValueAnimator animator) {
-        mAnimatorSet.add(animator);
-        animator.start();
+        result += (long) (index * ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE);
+        return result;
     }
 
     /**
@@ -814,33 +333,11 @@
             @Override
             public void onAnimationStart(Animator animation) {
                 mWasCancelled = false;
+                mAnimatorSet.add(animation);
             }
         };
     }
 
-    public static <T> T getChildTag(View child, int tag) {
-        return (T) child.getTag(tag);
-    }
-
-    /**
-     * Cancel the previous animator and get the duration of the new animation.
-     *
-     * @param duration the new duration
-     * @param previousAnimator the animator which was running before
-     * @return the new duration
-     */
-    private long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) {
-        long newDuration = duration;
-        if (previousAnimator != null) {
-            // We take either the desired length of the new animation or the remaining time of
-            // the previous animator, whichever is longer.
-            newDuration = Math.max(previousAnimator.getDuration()
-                    - previousAnimator.getCurrentPlayTime(), newDuration);
-            previousAnimator.cancel();
-        }
-        return newDuration;
-    }
-
     private void onAnimationFinished() {
         mHostLayout.onChildAnimationFinished();
         for (View v : mChildrenToClearFromOverlay) {
@@ -864,25 +361,25 @@
                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD) {
 
                 // This item is added, initialize it's properties.
-                StackViewState viewState = finalState
+                ExpandableViewState viewState = finalState
                         .getViewStateForView(changingView);
                 if (viewState == null) {
                     // The position for this child was never generated, let's continue.
                     continue;
                 }
-                finalState.applyState(changingView, viewState);
+                viewState.applyToView(changingView);
                 mNewAddChildren.add(changingView);
 
             } else if (event.animationType ==
                     NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_REMOVE) {
-                if (changingView.getVisibility() == View.GONE) {
+                if (changingView.getVisibility() != View.VISIBLE) {
                     removeFromOverlay(changingView);
                     continue;
                 }
 
                 // Find the amount to translate up. This is needed in order to understand the
                 // direction of the remove animation (either downwards or upwards)
-                StackViewState viewState = finalState
+                ExpandableViewState viewState = finalState
                         .getViewStateForView(event.viewAfterChangingView);
                 int actualHeight = changingView.getActualHeight();
                 // upwards by default
@@ -920,7 +417,7 @@
             } else if (event.animationType == NotificationStackScrollLayout
                     .AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR) {
                 // This item is added, initialize it's properties.
-                StackViewState viewState = finalState.getViewStateForView(changingView);
+                ExpandableViewState viewState = finalState.getViewStateForView(changingView);
                 mTmpState.copyFrom(viewState);
                 if (event.headsUpFromBottom) {
                     mTmpState.yTranslation = mHeadsUpAppearHeightBottom;
@@ -928,7 +425,7 @@
                     mTmpState.yTranslation = -mTmpState.height;
                 }
                 mHeadsUpAppearChildren.add(changingView);
-                finalState.applyState(changingView, mTmpState);
+                mTmpState.applyToView(changingView);
             } else if (event.animationType == NotificationStackScrollLayout
                             .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR ||
                     event.animationType == NotificationStackScrollLayout
@@ -942,12 +439,13 @@
                     // We temporarily enable Y animations, the real filter will be combined
                     // afterwards anyway
                     mAnimationFilter.animateY = true;
-                    startViewAnimations(changingView, mTmpState,
+                    mAnimationProperties.delay =
                             event.animationType == NotificationStackScrollLayout
                                     .AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
-                                            ? ANIMATION_DELAY_HEADS_UP
-                                            : 0,
-                            ANIMATION_DURATION_HEADS_UP_DISAPPEAR);
+                            ? ANIMATION_DELAY_HEADS_UP
+                            : 0;
+                    mAnimationProperties.duration = ANIMATION_DURATION_HEADS_UP_DISAPPEAR;
+                    mTmpState.animateTo(changingView, mAnimationProperties);
                     mChildrenToClearFromOverlay.add(changingView);
                 }
             }
@@ -1007,38 +505,6 @@
         }
     }
 
-    /**
-     * Get the end value of the height animation running on a view or the actualHeight
-     * if no animation is running.
-     */
-    public static int getFinalActualHeight(ExpandableView view) {
-        if (view == null) {
-            return 0;
-        }
-        ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
-        if (heightAnimator == null) {
-            return view.getActualHeight();
-        } else {
-            return getChildTag(view, TAG_END_HEIGHT);
-        }
-    }
-
-    /**
-     * Get the end value of the yTranslation animation running on a view or the yTranslation
-     * if no animation is running.
-     */
-    public static float getFinalTranslationY(View view) {
-        if (view == null) {
-            return 0;
-        }
-        ValueAnimator yAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y);
-        if (yAnimator == null) {
-            return view.getTranslationY();
-        } else {
-            return getChildTag(view, TAG_END_TRANSLATION_Y);
-        }
-    }
-
     public void setHeadsUpAppearHeightBottom(int headsUpAppearHeightBottom) {
         mHeadsUpAppearHeightBottom = headsUpAppearHeightBottom;
     }
@@ -1046,4 +512,8 @@
     public void setShadeExpanded(boolean shadeExpanded) {
         mShadeExpanded = shadeExpanded;
     }
+
+    public void setShelf(NotificationShelf shelf) {
+        mShelf = shelf;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
deleted file mode 100644
index ecdee4e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2015 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.statusbar.stack;
-
-/**
-* A state of an expandable view
-*/
-public class StackViewState extends ViewState {
-
-    // These are flags such that we can create masks for filtering.
-
-    public static final int LOCATION_UNKNOWN = 0x00;
-    public static final int LOCATION_FIRST_HUN = 0x01;
-    public static final int LOCATION_HIDDEN_TOP = 0x02;
-    public static final int LOCATION_MAIN_AREA = 0x04;
-    public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x08;
-    public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10;
-    /** The view isn't layouted at all. */
-    public static final int LOCATION_GONE = 0x40;
-
-    public int height;
-    public boolean dimmed;
-    public boolean dark;
-    public boolean hideSensitive;
-    public boolean belowSpeedBump;
-    public float shadowAlpha;
-
-    /**
-     * How much the child overlaps with the previous child on top. This is used to
-     * show the background properly when the child on top is translating away.
-     */
-    public int clipTopAmount;
-
-    /**
-     * The index of the view, only accounting for views not equal to GONE
-     */
-    public int notGoneIndex;
-
-    /**
-     * The location this view is currently rendered at.
-     *
-     * <p>See <code>LOCATION_</code> flags.</p>
-     */
-    public int location;
-
-    /**
-     * Whether a child in a group is being clipped at the bottom.
-     */
-    public boolean isBottomClipped;
-
-    @Override
-    public void copyFrom(ViewState viewState) {
-        super.copyFrom(viewState);
-        if (viewState instanceof StackViewState) {
-            StackViewState svs = (StackViewState) viewState;
-            height = svs.height;
-            dimmed = svs.dimmed;
-            shadowAlpha = svs.shadowAlpha;
-            dark = svs.dark;
-            hideSensitive = svs.hideSensitive;
-            belowSpeedBump = svs.belowSpeedBump;
-            clipTopAmount = svs.clipTopAmount;
-            notGoneIndex = svs.notGoneIndex;
-            location = svs.location;
-            isBottomClipped = svs.isBottomClipped;
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
index 5beaac3..8a5ddd4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ViewState.java
@@ -16,7 +16,18 @@
 
 package com.android.systemui.statusbar.stack;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.ValueAnimator;
 import android.view.View;
+import android.view.animation.Interpolator;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ExpandableView;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 /**
  * A state of a view. This can be used to apply a set of view properties to a view with
@@ -25,25 +36,519 @@
 */
 public class ViewState {
 
+    /**
+     * Some animation properties that can be used to update running animations but not creating
+     * any new ones.
+     */
+    protected static final AnimationProperties NO_NEW_ANIMATIONS = new AnimationProperties() {
+        AnimationFilter mAnimationFilter = new AnimationFilter();
+        @Override
+        public AnimationFilter getAnimationFilter() {
+            return mAnimationFilter;
+        }
+    };
+    private static final int TAG_ANIMATOR_TRANSLATION_X = R.id.translation_x_animator_tag;
+    private static final int TAG_ANIMATOR_TRANSLATION_Y = R.id.translation_y_animator_tag;
+    private static final int TAG_ANIMATOR_TRANSLATION_Z = R.id.translation_z_animator_tag;
+    private static final int TAG_ANIMATOR_ALPHA = R.id.alpha_animator_tag;
+    private static final int TAG_END_TRANSLATION_X = R.id.translation_x_animator_end_value_tag;
+    private static final int TAG_END_TRANSLATION_Y = R.id.translation_y_animator_end_value_tag;
+    private static final int TAG_END_TRANSLATION_Z = R.id.translation_z_animator_end_value_tag;
+    private static final int TAG_END_ALPHA = R.id.alpha_animator_end_value_tag;
+    private static final int TAG_START_TRANSLATION_X = R.id.translation_x_animator_start_value_tag;
+    private static final int TAG_START_TRANSLATION_Y = R.id.translation_y_animator_start_value_tag;
+    private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
+    private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
+
     public float alpha;
+    public float xTranslation;
     public float yTranslation;
     public float zTranslation;
     public boolean gone;
     public boolean hidden;
+    public float scaleX = 1.0f;
+    public float scaleY = 1.0f;
 
     public void copyFrom(ViewState viewState) {
         alpha = viewState.alpha;
+        xTranslation = viewState.xTranslation;
         yTranslation = viewState.yTranslation;
         zTranslation = viewState.zTranslation;
         gone = viewState.gone;
         hidden = viewState.hidden;
+        scaleX = viewState.scaleX;
+        scaleY = viewState.scaleY;
     }
 
     public void initFrom(View view) {
         alpha = view.getAlpha();
+        xTranslation = view.getTranslationX();
         yTranslation = view.getTranslationY();
         zTranslation = view.getTranslationZ();
         gone = view.getVisibility() == View.GONE;
         hidden = false;
+        scaleX = view.getScaleX();
+        scaleY = view.getScaleY();
+    }
+
+    /**
+     * Applies a {@link ViewState} to a normal view.
+     */
+    public void applyToView(View view) {
+        if (this.gone) {
+            // don't do anything with it
+            return;
+        }
+        boolean becomesInvisible = this.alpha == 0.0f || this.hidden;
+        boolean animatingAlpha = isAnimating(view, TAG_ANIMATOR_ALPHA);
+        if (animatingAlpha) {
+            updateAlphaAnimation(view);
+        } else if (view.getAlpha() != this.alpha) {
+            // apply layer type
+            boolean becomesFullyVisible = this.alpha == 1.0f;
+            boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
+                    && view.hasOverlappingRendering();
+            int layerType = view.getLayerType();
+            int newLayerType = newLayerTypeIsHardware
+                    ? View.LAYER_TYPE_HARDWARE
+                    : View.LAYER_TYPE_NONE;
+            if (layerType != newLayerType) {
+                view.setLayerType(newLayerType, null);
+            }
+
+            // apply alpha
+            view.setAlpha(this.alpha);
+        }
+
+        // apply visibility
+        int oldVisibility = view.getVisibility();
+        int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
+        if (newVisibility != oldVisibility) {
+            if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) {
+                // We don't want views to change visibility when they are animating to GONE
+                view.setVisibility(newVisibility);
+            }
+        }
+
+        // apply xTranslation
+        boolean animatingX = isAnimating(view, TAG_ANIMATOR_TRANSLATION_X);
+        if (animatingX) {
+            updateAnimationX(view);
+        } else if (view.getTranslationX() != this.xTranslation){
+            view.setTranslationX(this.xTranslation);
+        }
+
+        // apply yTranslation
+        boolean animatingY = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Y);
+        if (animatingY) {
+            updateAnimationY(view);
+        } else if (view.getTranslationY() != this.yTranslation) {
+            view.setTranslationY(this.yTranslation);
+        }
+
+        // apply zTranslation
+        boolean animatingZ = isAnimating(view, TAG_ANIMATOR_TRANSLATION_Z);
+        if (animatingZ) {
+            updateAnimationZ(view);
+        } else if (view.getTranslationZ() != this.zTranslation) {
+            view.setTranslationZ(this.zTranslation);
+        }
+
+        // apply scaleX
+        if (view.getScaleX() != this.scaleX) {
+            view.setScaleX(this.scaleX);
+        }
+
+        // apply scaleY
+        if (view.getScaleY() != this.scaleY) {
+            view.setScaleY(this.scaleY);
+        }
+    }
+
+    private boolean isAnimating(View view, int tag) {
+        return getChildTag(view, tag) != null;
+    }
+
+    /**
+     * Start an animation to this viewstate
+     * @param child the view to animate
+     * @param animationProperties the properties of the animation
+     */
+    public void animateTo(View child, AnimationProperties animationProperties) {
+        boolean wasVisible = child.getVisibility() == View.VISIBLE;
+        final float alpha = this.alpha;
+        if (!wasVisible && (alpha != 0 || child.getAlpha() != 0)
+                && !this.gone && !this.hidden) {
+            child.setVisibility(View.VISIBLE);
+        }
+        float childAlpha = child.getAlpha();
+        boolean alphaChanging = this.alpha != childAlpha;
+        if (child instanceof ExpandableView) {
+            // We don't want views to change visibility when they are animating to GONE
+            alphaChanging &= !((ExpandableView) child).willBeGone();
+        }
+
+        // start translationX animation
+        if (child.getTranslationX() != this.xTranslation) {
+            startXTranslationAnimation(child, animationProperties);
+        } else {
+            abortAnimation(child, TAG_ANIMATOR_TRANSLATION_X);
+        }
+
+        // start translationY animation
+        if (child.getTranslationY() != this.yTranslation) {
+            startYTranslationAnimation(child, animationProperties);
+        } else {
+            abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Y);
+        }
+
+        // start translationZ animation
+        if (child.getTranslationZ() != this.zTranslation) {
+            startZTranslationAnimation(child, animationProperties);
+        } else {
+            abortAnimation(child, TAG_ANIMATOR_TRANSLATION_Z);
+        }
+
+        // start alpha animation
+        if (alphaChanging) {
+            startAlphaAnimation(child, animationProperties);
+        }  else {
+            abortAnimation(child, TAG_ANIMATOR_ALPHA);
+        }
+    }
+
+    private void updateAlphaAnimation(View view) {
+        startAlphaAnimation(view, NO_NEW_ANIMATIONS);
+    }
+
+    private void startAlphaAnimation(final View child, AnimationProperties properties) {
+        Float previousStartValue = getChildTag(child,TAG_START_ALPHA);
+        Float previousEndValue = getChildTag(child,TAG_END_ALPHA);
+        final float newEndValue = this.alpha;
+        if (previousEndValue != null && previousEndValue == newEndValue) {
+            return;
+        }
+        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_ALPHA);
+        AnimationFilter filter = properties.getAnimationFilter();
+        if (!filter.animateAlpha) {
+            // just a local update was performed
+            if (previousAnimator != null) {
+                // we need to increase all animation keyframes of the previous animator by the
+                // relative change to the end value
+                PropertyValuesHolder[] values = previousAnimator.getValues();
+                float relativeDiff = newEndValue - previousEndValue;
+                float newStartValue = previousStartValue + relativeDiff;
+                values[0].setFloatValues(newStartValue, newEndValue);
+                child.setTag(TAG_START_ALPHA, newStartValue);
+                child.setTag(TAG_END_ALPHA, newEndValue);
+                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+                return;
+            } else {
+                // no new animation needed, let's just apply the value
+                child.setAlpha(newEndValue);
+                if (newEndValue == 0) {
+                    child.setVisibility(View.INVISIBLE);
+                }
+            }
+        }
+
+        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.ALPHA,
+                child.getAlpha(), newEndValue);
+        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        // Handle layer type
+        child.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+        animator.addListener(new AnimatorListenerAdapter() {
+            public boolean mWasCancelled;
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                child.setLayerType(View.LAYER_TYPE_NONE, null);
+                if (newEndValue == 0 && !mWasCancelled) {
+                    child.setVisibility(View.INVISIBLE);
+                }
+                // remove the tag when the animation is finished
+                child.setTag(TAG_ANIMATOR_ALPHA, null);
+                child.setTag(TAG_START_ALPHA, null);
+                child.setTag(TAG_END_ALPHA, null);
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mWasCancelled = true;
+            }
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                mWasCancelled = false;
+            }
+        });
+        long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
+        animator.setDuration(newDuration);
+        if (properties.delay > 0 && (previousAnimator == null
+                || previousAnimator.getAnimatedFraction() == 0)) {
+            animator.setStartDelay(properties.delay);
+        }
+        AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
+        if (listener != null) {
+            animator.addListener(listener);
+        }
+
+        startAnimator(animator, listener);
+        child.setTag(TAG_ANIMATOR_ALPHA, animator);
+        child.setTag(TAG_START_ALPHA, child.getAlpha());
+        child.setTag(TAG_END_ALPHA, newEndValue);
+    }
+
+    private void updateAnimationZ(View view) {
+        startZTranslationAnimation(view, NO_NEW_ANIMATIONS);
+    }
+
+    private void startZTranslationAnimation(final View child, AnimationProperties properties) {
+        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Z);
+        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Z);
+        float newEndValue = this.zTranslation;
+        if (previousEndValue != null && previousEndValue == newEndValue) {
+            return;
+        }
+        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Z);
+        AnimationFilter filter = properties.getAnimationFilter();
+        if (!filter.animateZ) {
+            // just a local update was performed
+            if (previousAnimator != null) {
+                // we need to increase all animation keyframes of the previous animator by the
+                // relative change to the end value
+                PropertyValuesHolder[] values = previousAnimator.getValues();
+                float relativeDiff = newEndValue - previousEndValue;
+                float newStartValue = previousStartValue + relativeDiff;
+                values[0].setFloatValues(newStartValue, newEndValue);
+                child.setTag(TAG_START_TRANSLATION_Z, newStartValue);
+                child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
+                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+                return;
+            } else {
+                // no new animation needed, let's just apply the value
+                child.setTranslationZ(newEndValue);
+            }
+        }
+
+        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Z,
+                child.getTranslationZ(), newEndValue);
+        animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
+        animator.setDuration(newDuration);
+        if (properties.delay > 0 && (previousAnimator == null
+                || previousAnimator.getAnimatedFraction() == 0)) {
+            animator.setStartDelay(properties.delay);
+        }
+        AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
+        if (listener != null) {
+            animator.addListener(listener);
+        }
+        // remove the tag when the animation is finished
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                child.setTag(TAG_ANIMATOR_TRANSLATION_Z, null);
+                child.setTag(TAG_START_TRANSLATION_Z, null);
+                child.setTag(TAG_END_TRANSLATION_Z, null);
+            }
+        });
+        startAnimator(animator, listener);
+        child.setTag(TAG_ANIMATOR_TRANSLATION_Z, animator);
+        child.setTag(TAG_START_TRANSLATION_Z, child.getTranslationZ());
+        child.setTag(TAG_END_TRANSLATION_Z, newEndValue);
+    }
+
+    private void updateAnimationX(View view) {
+        startXTranslationAnimation(view, NO_NEW_ANIMATIONS);
+    }
+
+    private void startXTranslationAnimation(final View child, AnimationProperties properties) {
+        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_X);
+        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_X);
+        float newEndValue = this.xTranslation;
+        if (previousEndValue != null && previousEndValue == newEndValue) {
+            return;
+        }
+        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_X);
+        AnimationFilter filter = properties.getAnimationFilter();
+        if (!filter.animateX) {
+            // just a local update was performed
+            if (previousAnimator != null) {
+                // we need to increase all animation keyframes of the previous animator by the
+                // relative change to the end value
+                PropertyValuesHolder[] values = previousAnimator.getValues();
+                float relativeDiff = newEndValue - previousEndValue;
+                float newStartValue = previousStartValue + relativeDiff;
+                values[0].setFloatValues(newStartValue, newEndValue);
+                child.setTag(TAG_START_TRANSLATION_X, newStartValue);
+                child.setTag(TAG_END_TRANSLATION_X, newEndValue);
+                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+                return;
+            } else {
+                // no new animation needed, let's just apply the value
+                child.setTranslationX(newEndValue);
+                return;
+            }
+        }
+
+        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_X,
+                child.getTranslationX(), newEndValue);
+        Interpolator customInterpolator = properties.getCustomInterpolator(child,
+                View.TRANSLATION_X);
+        Interpolator interpolator =  customInterpolator != null ? customInterpolator
+                : Interpolators.FAST_OUT_SLOW_IN;
+        animator.setInterpolator(interpolator);
+        long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
+        animator.setDuration(newDuration);
+        if (properties.delay > 0 && (previousAnimator == null
+                || previousAnimator.getAnimatedFraction() == 0)) {
+            animator.setStartDelay(properties.delay);
+        }
+        AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
+        if (listener != null) {
+            animator.addListener(listener);
+        }
+        // remove the tag when the animation is finished
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                child.setTag(TAG_ANIMATOR_TRANSLATION_X, null);
+                child.setTag(TAG_START_TRANSLATION_X, null);
+                child.setTag(TAG_END_TRANSLATION_X, null);
+            }
+        });
+        startAnimator(animator, listener);
+        child.setTag(TAG_ANIMATOR_TRANSLATION_X, animator);
+        child.setTag(TAG_START_TRANSLATION_X, child.getTranslationX());
+        child.setTag(TAG_END_TRANSLATION_X, newEndValue);
+    }
+
+    private void updateAnimationY(View view) {
+        startYTranslationAnimation(view, NO_NEW_ANIMATIONS);
+    }
+
+    private void startYTranslationAnimation(final View child, AnimationProperties properties) {
+        Float previousStartValue = getChildTag(child,TAG_START_TRANSLATION_Y);
+        Float previousEndValue = getChildTag(child,TAG_END_TRANSLATION_Y);
+        float newEndValue = this.yTranslation;
+        if (previousEndValue != null && previousEndValue == newEndValue) {
+            return;
+        }
+        ObjectAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y);
+        AnimationFilter filter = properties.getAnimationFilter();
+        if (!filter.animateY) {
+            // just a local update was performed
+            if (previousAnimator != null) {
+                // we need to increase all animation keyframes of the previous animator by the
+                // relative change to the end value
+                PropertyValuesHolder[] values = previousAnimator.getValues();
+                float relativeDiff = newEndValue - previousEndValue;
+                float newStartValue = previousStartValue + relativeDiff;
+                values[0].setFloatValues(newStartValue, newEndValue);
+                child.setTag(TAG_START_TRANSLATION_Y, newStartValue);
+                child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
+                previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
+                return;
+            } else {
+                // no new animation needed, let's just apply the value
+                child.setTranslationY(newEndValue);
+                return;
+            }
+        }
+
+        ObjectAnimator animator = ObjectAnimator.ofFloat(child, View.TRANSLATION_Y,
+                child.getTranslationY(), newEndValue);
+        Interpolator customInterpolator = properties.getCustomInterpolator(child,
+                View.TRANSLATION_Y);
+        Interpolator interpolator =  customInterpolator != null ? customInterpolator
+                : Interpolators.FAST_OUT_SLOW_IN;
+        animator.setInterpolator(interpolator);
+        long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
+        animator.setDuration(newDuration);
+        if (properties.delay > 0 && (previousAnimator == null
+                || previousAnimator.getAnimatedFraction() == 0)) {
+            animator.setStartDelay(properties.delay);
+        }
+        AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
+        if (listener != null) {
+            animator.addListener(listener);
+        }
+        // remove the tag when the animation is finished
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                HeadsUpManager.setIsClickedNotification(child, false);
+                child.setTag(TAG_ANIMATOR_TRANSLATION_Y, null);
+                child.setTag(TAG_START_TRANSLATION_Y, null);
+                child.setTag(TAG_END_TRANSLATION_Y, null);
+                onYTranslationAnimationFinished();
+            }
+        });
+        startAnimator(animator, listener);
+        child.setTag(TAG_ANIMATOR_TRANSLATION_Y, animator);
+        child.setTag(TAG_START_TRANSLATION_Y, child.getTranslationY());
+        child.setTag(TAG_END_TRANSLATION_Y, newEndValue);
+    }
+
+    protected void onYTranslationAnimationFinished() {
+    }
+
+    protected void startAnimator(Animator animator, AnimatorListenerAdapter listener) {
+        if (listener != null) {
+            // Even if there's a delay we'd want to notify it of the start immediately.
+            listener.onAnimationStart(animator);
+        }
+        animator.start();
+    }
+
+    public static <T> T getChildTag(View child, int tag) {
+        return (T) child.getTag(tag);
+    }
+
+    protected void abortAnimation(View child, int animatorTag) {
+        Animator previousAnimator = getChildTag(child, animatorTag);
+        if (previousAnimator != null) {
+            previousAnimator.cancel();
+        }
+    }
+
+    /**
+     * Cancel the previous animator and get the duration of the new animation.
+     *
+     * @param duration the new duration
+     * @param previousAnimator the animator which was running before
+     * @return the new duration
+     */
+    protected long cancelAnimatorAndGetNewDuration(long duration, ValueAnimator previousAnimator) {
+        long newDuration = duration;
+        if (previousAnimator != null) {
+            // We take either the desired length of the new animation or the remaining time of
+            // the previous animator, whichever is longer.
+            newDuration = Math.max(previousAnimator.getDuration()
+                    - previousAnimator.getCurrentPlayTime(), newDuration);
+            previousAnimator.cancel();
+        }
+        return newDuration;
+    }
+
+    /**
+     * Get the end value of the yTranslation animation running on a view or the yTranslation
+     * if no animation is running.
+     */
+    public static float getFinalTranslationY(View view) {
+        if (view == null) {
+            return 0;
+        }
+        ValueAnimator yAnimator = getChildTag(view, TAG_ANIMATOR_TRANSLATION_Y);
+        if (yAnimator == null) {
+            return view.getTranslationY();
+        } else {
+            return getChildTag(view, TAG_END_TRANSLATION_Y);
+        }
+    }
+
+    public static boolean isAnimatingY(ExpandableView child) {
+        return getChildTag(child, TAG_ANIMATOR_TRANSLATION_Y) != null;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
index c20cc84..5393d60 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogController.java
@@ -78,6 +78,7 @@
         AudioSystem.STREAM_SYSTEM_ENFORCED,
         AudioSystem.STREAM_TTS,
         AudioSystem.STREAM_VOICE_CALL,
+        AudioSystem.STREAM_ACCESSIBILITY,
     };
 
     private final HandlerThread mWorkerThread;
@@ -562,6 +563,22 @@
                     .sendToTarget();
             mWorker.sendEmptyMessage(W.DISMISS_REQUESTED);
         }
+
+        @Override
+        public void setA11yMode(int mode) {
+            if (D.BUG) Log.d(TAG, "setA11yMode to " + mode);
+            if (mDestroyed) return;
+            switch (mode) {
+                case VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME:
+                    // "legacy" mode
+                    break;
+                case VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME:
+                    break;
+                default:
+                    Log.e(TAG, "Invalid accessibility mode " + mode);
+                    break;
+            }
+        }
     }
 
     private final class W extends Handler {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java
index 74b6127..a9f811b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java
@@ -19,39 +19,44 @@
 import android.test.suitebuilder.annotation.SmallTest;
 import com.android.systemui.SysuiTestCase;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 
+import org.junit.Test;
+
 @SmallTest
 public class TaskGridLayoutAlgorithmTest extends SysuiTestCase {
 
-    public void testMethodName_ExpectedBehavior() {
-        assertTrue(true);
-    }
+    private static final List<Integer> ZERO_MARGIN = TaskGridLayoutAlgorithm.ZERO_MARGIN;
 
+    @Test
     public void testOneTile() {
         List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
-                1, 1000, 500, false /* allowLineOfThree */, 0 /* padding */);
+                1, 1000, 1000, 1 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0);
         assertEquals(1, rects.size());
         Rect singleRect = rects.get(0);
         assertEquals(1000, singleRect.width());
     }
 
+    @Test
     public void testTwoTilesLandscape() {
         List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
-                2, 1200, 500, false /* allowLineOfThree */, 0 /* padding */);
+                2, 1200, 500, 1.2f /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0);
         assertEquals(2, rects.size());
         for (Rect rect : rects) {
             assertEquals(600, rect.width());
-            assertEquals(500, rect.height());
+            assertEquals(499, rect.height());
         }
     }
 
+    @Test
     public void testTwoTilesLandscapeWithPadding() {
         List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
-                2, 1200, 500, false /* allowLineOfThree */, 10 /* padding */);
+                2, 1200, 500, 1.19f /* screenRatio */, 10 /* padding */, ZERO_MARGIN, 0);
         assertEquals(2, rects.size());
         Rect rectA = rects.get(0);
         Rect rectB = rects.get(1);
@@ -60,46 +65,46 @@
         assertEquals(605, rectB.left);
     }
 
+    @Test
     public void testTwoTilesPortrait() {
         List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
-                2, 500, 1200, false /* allowLineOfThree */, 0 /* padding */);
+                2, 500, 1200, 1 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0);
         assertEquals(2, rects.size());
         for (Rect rect : rects) {
-            assertEquals(500, rect.width());
-            assertEquals(600, rect.height());
-        }
-    }
-
-    public void testThreeTiles() {
-        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
-                3, 1200, 500, false /* allowLineOfThree */, 0 /* padding */);
-        assertEquals(3, rects.size());
-        for (Rect rect : rects) {
-            assertEquals(600, rect.width());
+            assertEquals(250, rect.width());
             assertEquals(250, rect.height());
         }
-        // The third tile should be on the second line, in the middle.
-        Rect rectC = rects.get(2);
-        assertEquals(300, rectC.left);
-        assertEquals(250, rectC.top);
     }
 
+    @Test
+    public void testThreeTiles() {
+        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
+                3, 1200, 500, 2 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0);
+        assertEquals(3, rects.size());
+        for (Rect rect : rects) {
+            assertEquals(400, rect.width());
+            assertEquals(200, rect.height());
+        }
+    }
+
+    @Test
     public void testFourTiles() {
         List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
-                4, 1200, 500, false /* allowLineOfThree */, 0 /* padding */);
+                4, 1200, 500, 2.4f /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0);
         assertEquals(4, rects.size());
         for (Rect rect : rects) {
             assertEquals(600, rect.width());
-            assertEquals(250, rect.height());
+            assertEquals(249, rect.height());
         }
         Rect rectD = rects.get(3);
         assertEquals(600, rectD.left);
         assertEquals(250, rectD.top);
     }
 
+    @Test
     public void testNineTiles() {
         List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
-                9, 1200, 600, false /* allowLineOfThree */, 0 /* padding */);
+                9, 1200, 600, 2 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0);
         assertEquals(9, rects.size());
         for (Rect rect : rects) {
             assertEquals(400, rect.width());
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index de70026..5eceb9f 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -5342,6 +5342,7 @@
     // notify only this one new request of the current state
     protected void notifyNetworkCallback(NetworkAgentInfo nai, NetworkRequestInfo nri) {
         int notifyType = ConnectivityManager.CALLBACK_AVAILABLE;
+        mHandler.removeMessages(EVENT_TIMEOUT_NETWORK_REQUEST, nri);
         if (nri.mPendingIntent == null) {
             callCallbackForRequest(nri, nai, notifyType, 0);
         } else {
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index f4ddc06..1398530 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -244,21 +244,29 @@
             if (DEBUG) Slog.v(TAG, "Parent does not have a screen lock");
             return;
         }
+        // Do not tie when the parent has no SID (but does have a screen lock).
+        // This can only happen during an upgrade path where SID is yet to be
+        // generated when the user unlocks for the first time.
+        try {
+            if (getGateKeeperService().getSecureUserId(parentId) == 0) {
+                return;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to talk to GateKeeper service", e);
+            return;
+        }
         if (DEBUG) Slog.v(TAG, "Tie managed profile to parent now!");
         byte[] randomLockSeed = new byte[] {};
         try {
             randomLockSeed = SecureRandom.getInstance("SHA1PRNG").generateSeed(40);
             String newPassword = String.valueOf(HexEncoding.encode(randomLockSeed));
-            tieProfileLockToParent(managedUserId, newPassword);
             setLockPasswordInternal(newPassword, managedUserPassword, managedUserId);
             // We store a private credential for the managed user that's unlocked by the primary
             // account holder's credential. As such, the user will never be prompted to enter this
             // password directly, so we always store a password.
             setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
                     DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, managedUserId);
-        } catch (KeyStoreException e) {
-            // Bug: 32490092
-            Slog.e(TAG, "Not able to set keys to keystore", e);
+            tieProfileLockToParent(managedUserId, newPassword);
         } catch (NoSuchAlgorithmException | RemoteException e) {
             Slog.e(TAG, "Fail to tie managed profile", e);
             // Nothing client can do to fix this issue, so we do not throw exception out
@@ -779,7 +787,6 @@
     }
 
     private void unlockChildProfile(int profileHandle) throws RemoteException {
-        if (DEBUG) Slog.v(TAG, "Unlock child profile");
         try {
             doVerifyPassword(getDecryptedPasswordForTiedProfile(profileHandle), false,
                     0 /* no challenge */, profileHandle, null /* progressCallback */);
@@ -1039,7 +1046,7 @@
         }
     }
 
-    private void tieProfileLockToParent(int userId, String password) throws KeyStoreException {
+    private void tieProfileLockToParent(int userId, String password) {
         if (DEBUG) Slog.v(TAG, "tieProfileLockToParent for user: " + userId);
         byte[] randomLockSeed = password.getBytes(StandardCharsets.UTF_8);
         byte[] encryptionResult;
@@ -1081,7 +1088,7 @@
                 keyStore.deleteEntry(LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userId);
             }
         } catch (CertificateException | UnrecoverableKeyException
-                | IOException | BadPaddingException | IllegalBlockSizeException
+                | IOException | BadPaddingException | IllegalBlockSizeException | KeyStoreException
                 | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
             throw new RuntimeException("Failed to encrypt key", e);
         }
@@ -1223,11 +1230,7 @@
         } finally {
             if (managedUserId != -1 && managedUserDecryptedPassword != null) {
                 if (DEBUG) Slog.v(TAG, "Restore tied profile lock");
-                try {
-                    tieProfileLockToParent(managedUserId, managedUserDecryptedPassword);
-                } catch (KeyStoreException e) {
-                    throw new RuntimeException("Failed to tie profile lock", e);
-                }
+                tieProfileLockToParent(managedUserId, managedUserDecryptedPassword);
             }
         }
     }
diff --git a/services/core/java/com/android/server/accounts/AccountsDb.java b/services/core/java/com/android/server/accounts/AccountsDb.java
index cb594f6..a160b3a 100644
--- a/services/core/java/com/android/server/accounts/AccountsDb.java
+++ b/services/core/java/com/android/server/accounts/AccountsDb.java
@@ -54,7 +54,7 @@
     private static final String DATABASE_NAME = "accounts.db";
     private static final int PRE_N_DATABASE_VERSION = 9;
     private static final int CE_DATABASE_VERSION = 10;
-    private static final int DE_DATABASE_VERSION = 1;
+    private static final int DE_DATABASE_VERSION = 2; // Added visibility support in O.
 
 
     static final String TABLE_ACCOUNTS = "accounts";
@@ -73,6 +73,11 @@
     private static final String AUTHTOKENS_TYPE = "type";
     private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
 
+    private static final String TABLE_VISIBILITY = "visibility";
+    private static final String VISIBILITY_ACCOUNTS_ID = "accounts_id";
+    private static final String VISIBILITY_UID = "_uid";
+    private static final String VISIBILITY_VALUE = "value";
+
     private static final String TABLE_GRANTS = "grants";
     private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
     private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
@@ -153,14 +158,12 @@
             + " AND " + ACCOUNTS_NAME + "=?"
             + " AND " + ACCOUNTS_TYPE + "=?";
 
-    private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT =
-            AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
+    private static final String SELECTION_ACCOUNTS_ID_BY_ACCOUNT =
+        "accounts_id=(select _id FROM accounts WHERE name=? AND type=?)";
 
-    private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE,
-            AUTHTOKENS_AUTHTOKEN};
+    private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN =
+            {AUTHTOKENS_TYPE, AUTHTOKENS_AUTHTOKEN};
 
-    private static final String SELECTION_USERDATA_BY_ACCOUNT =
-            EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
     private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE};
 
     private static final String ACCOUNT_ACCESS_GRANTS = ""
@@ -334,7 +337,7 @@
         HashMap<String, String> authTokensForAccount = new HashMap<>();
         Cursor cursor = db.query(CE_TABLE_AUTHTOKENS,
                 COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
-                SELECTION_AUTHTOKENS_BY_ACCOUNT,
+                SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
                 new String[] {account.name, account.type},
                 null, null, null);
         try {
@@ -438,7 +441,7 @@
         String[] selectionArgs = {account.name, account.type};
         try (Cursor cursor = db.query(CE_TABLE_EXTRAS,
                 COLUMNS_EXTRAS_KEY_AND_VALUE,
-                SELECTION_USERDATA_BY_ACCOUNT,
+                SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
                 selectionArgs,
                 null, null, null)) {
             while (cursor.moveToNext()) {
@@ -523,6 +526,8 @@
             createSharedAccountsTable(db);
             createAccountsDeletionTrigger(db);
             createDebugTable(db);
+            createAccountsVisibilityTable(db);
+            createAccountsDeletionVisibilityCleanupTrigger(db);
         }
 
         private void createSharedAccountsTable(SQLiteDatabase db) {
@@ -551,6 +556,14 @@
                     +   "," + GRANTS_GRANTEE_UID + "))");
         }
 
+        private void createAccountsVisibilityTable(SQLiteDatabase db) {
+            db.execSQL("CREATE TABLE " + TABLE_VISIBILITY + " ( "
+                  + VISIBILITY_ACCOUNTS_ID + " INTEGER NOT NULL, "
+                  + VISIBILITY_UID + " TEXT NOT NULL, "
+                  + VISIBILITY_VALUE + " INTEGER, "
+                  + "PRIMARY KEY(" + VISIBILITY_ACCOUNTS_ID + "," + VISIBILITY_UID + "))");
+        }
+
         static void createDebugTable(SQLiteDatabase db) {
             db.execSQL("CREATE TABLE " + TABLE_DEBUG + " ( "
                     + ACCOUNTS_ID + " INTEGER,"
@@ -563,10 +576,26 @@
                     + DEBUG_TABLE_TIMESTAMP + ")");
         }
 
+        private void createAccountsDeletionVisibilityCleanupTrigger(SQLiteDatabase db) {
+            db.execSQL(""
+                   + " CREATE TRIGGER "
+                   + TABLE_ACCOUNTS + "DeleteVisibility DELETE ON " + TABLE_ACCOUNTS
+                   + " BEGIN"
+                   + "   DELETE FROM " + TABLE_VISIBILITY
+                   + "     WHERE " + VISIBILITY_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+                   + " END");
+        }
+
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             Log.i(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
 
+            if (oldVersion == 1) {
+              createAccountsVisibilityTable(db);
+              createAccountsDeletionVisibilityCleanupTrigger(db);
+              ++oldVersion;
+            }
+
             if (oldVersion != newVersion) {
                 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
             }
@@ -861,6 +890,84 @@
                 new String[] {Integer.toString(uid)}) > 0;
     }
 
+    boolean setAccountVisibility(long accountId, int uid, int visibility) {
+        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(VISIBILITY_ACCOUNTS_ID, String.valueOf(accountId));
+        values.put(VISIBILITY_UID, String.valueOf(uid));
+        values.put(VISIBILITY_VALUE, String.valueOf(visibility));
+        return (db.replace(TABLE_VISIBILITY, VISIBILITY_VALUE, values) != -1);
+    }
+
+    Integer findAccountVisibility(Account account, int uid) {
+        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+        final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
+                SELECTION_ACCOUNTS_ID_BY_ACCOUNT + " AND " + VISIBILITY_UID + "=? ",
+                new String[] {account.name, account.type, String.valueOf(uid)}, null, null, null);
+        try {
+            while (cursor.moveToNext()) {
+                return cursor.getInt(0);
+            }
+        } finally {
+            cursor.close();
+        }
+        return null;
+    }
+
+    Integer findAccountVisibility(long accountId, int uid) {
+        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+        final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
+                VISIBILITY_ACCOUNTS_ID + "=? AND " + VISIBILITY_UID + "=? ",
+                new String[] {String.valueOf(accountId), String.valueOf(uid)}, null, null, null);
+        try {
+            while (cursor.moveToNext()) {
+                return cursor.getInt(0);
+            }
+        } finally {
+            cursor.close();
+        }
+        return null;
+    }
+
+    Account findDeAccountByAccountId(long accountId) {
+        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+        final Cursor cursor = db.query(TABLE_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE},
+                ACCOUNTS_ID + "=? ", new String[] {String.valueOf(accountId)}, null, null, null);
+        try {
+            while (cursor.moveToNext()) {
+                return new Account(cursor.getString(0), cursor.getString(1));
+            }
+        } finally {
+            cursor.close();
+        }
+        return null;
+    }
+
+    /**
+     * Returns a map from uid to visibility value.
+     */
+    Map<Integer, Integer> findAccountVisibilityForAccountId(long accountId) {
+        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
+        Map<Integer, Integer> result = new HashMap<>();
+        final Cursor cursor = db.query(TABLE_VISIBILITY,
+                new String[] {VISIBILITY_UID, VISIBILITY_VALUE}, VISIBILITY_ACCOUNTS_ID + "=? ",
+                new String[] {String.valueOf(accountId)}, null, null, null);
+        try {
+            while (cursor.moveToNext()) {
+                result.put(cursor.getInt(0), cursor.getInt(1));
+            }
+        } finally {
+            cursor.close();
+        }
+        return result;
+    }
+
+    boolean deleteAccountVisibilityForUid(int uid) {
+        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
+        return db.delete(TABLE_VISIBILITY, VISIBILITY_UID + "=? ",
+                new String[] {Integer.toString(uid)}) > 0;
+    }
+
     long insertOrReplaceMetaAuthTypeAndUid(String authenticatorType, int uid) {
         SQLiteDatabase db = mDeDatabase.getWritableDatabase();
         ContentValues values = new ContentValues();
@@ -1199,4 +1306,4 @@
         return new AccountsDb(deDatabaseHelper, context, preNDatabaseFile);
     }
 
-}
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 1beffc4..d085a47 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1522,7 +1522,7 @@
     static final int PERSIST_URI_GRANTS_MSG = 38;
     static final int REQUEST_ALL_PSS_MSG = 39;
     static final int START_PROFILES_MSG = 40;
-    static final int UPDATE_TIME = 41;
+    static final int UPDATE_TIME_PREFERENCE_MSG = 41;
     static final int SYSTEM_USER_START_MSG = 42;
     static final int SYSTEM_USER_CURRENT_MSG = 43;
     static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
@@ -2028,15 +2028,18 @@
                 }
                 break;
             }
-            case UPDATE_TIME: {
+            case UPDATE_TIME_PREFERENCE_MSG: {
+                // The user's time format preference might have changed.
+                // For convenience we re-use the Intent extra values.
                 synchronized (ActivityManagerService.this) {
-                    for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
+                    for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
                         ProcessRecord r = mLruProcesses.get(i);
                         if (r.thread != null) {
                             try {
-                                r.thread.updateTimePrefs(msg.arg1 == 0 ? false : true);
+                                r.thread.updateTimePrefs(msg.arg1);
                             } catch (RemoteException ex) {
-                                Slog.w(TAG, "Failed to update preferences for: " + r.info.processName);
+                                Slog.w(TAG, "Failed to update preferences for: "
+                                        + r.info.processName);
                             }
                         }
                     }
@@ -18171,11 +18174,20 @@
                     mHandler.sendEmptyMessage(UPDATE_TIME_ZONE);
                     break;
                 case Intent.ACTION_TIME_CHANGED:
-                    // If the user set the time, let all running processes know.
-                    final int is24Hour =
-                            intent.getBooleanExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT, false) ? 1
-                                    : 0;
-                    mHandler.sendMessage(mHandler.obtainMessage(UPDATE_TIME, is24Hour, 0));
+                    // EXTRA_TIME_PREF_24_HOUR_FORMAT is optional so we must distinguish between
+                    // the tri-state value it may contain and "unknown".
+                    // For convenience we re-use the Intent extra values.
+                    final int NO_EXTRA_VALUE_FOUND = -1;
+                    final int timeFormatPreferenceMsgValue = intent.getIntExtra(
+                            Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
+                            NO_EXTRA_VALUE_FOUND /* defaultValue */);
+                    // Only send a message if the time preference is available.
+                    if (timeFormatPreferenceMsgValue != NO_EXTRA_VALUE_FOUND) {
+                        Message updateTimePreferenceMsg =
+                                mHandler.obtainMessage(UPDATE_TIME_PREFERENCE_MSG,
+                                        timeFormatPreferenceMsgValue, 0);
+                        mHandler.sendMessage(updateTimePreferenceMsg);
+                    }
                     BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
                     synchronized (stats) {
                         stats.noteCurrentTimeChangedLocked();
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index b53a680..eb34669 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -3779,6 +3779,7 @@
             findTaskToMoveToFrontLocked(task, 0, null, reason,
                     lockTaskModeState != LOCK_TASK_MODE_NONE);
             resumeFocusedStackTopActivityLocked();
+            mWindowManager.executeAppTransition();
         } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
             handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, task.getStackId(),
                     true /* forceNonResizable */);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index da016da..67f3614 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -139,7 +139,9 @@
  *
  * @hide
  */
-public class AudioService extends IAudioService.Stub {
+public class AudioService extends IAudioService.Stub
+        implements AccessibilityManager.TouchExplorationStateChangeListener,
+            AccessibilityManager.AccessibilityStateChangeListener{
 
     private static final String TAG = "AudioService";
 
@@ -272,7 +274,8 @@
         15, // STREAM_BLUETOOTH_SCO
         7,  // STREAM_SYSTEM_ENFORCED
         15, // STREAM_DTMF
-        15  // STREAM_TTS
+        15, // STREAM_TTS
+        15  // STREAM_ACCESSIBILITY
     };
 
     /** Minimum volume index values for audio streams */
@@ -286,7 +289,8 @@
         0,  // STREAM_BLUETOOTH_SCO
         0,  // STREAM_SYSTEM_ENFORCED
         0,  // STREAM_DTMF
-        0   // STREAM_TTS
+        0,  // STREAM_TTS
+        0   // STREAM_ACCESSIBILITY
     };
 
     /* mStreamVolumeAlias[] indicates for each stream if it uses the volume settings
@@ -308,7 +312,8 @@
         AudioSystem.STREAM_BLUETOOTH_SCO,   // STREAM_BLUETOOTH_SCO
         AudioSystem.STREAM_RING,            // STREAM_SYSTEM_ENFORCED
         AudioSystem.STREAM_RING,            // STREAM_DTMF
-        AudioSystem.STREAM_MUSIC            // STREAM_TTS
+        AudioSystem.STREAM_MUSIC,           // STREAM_TTS
+        AudioSystem.STREAM_MUSIC            // STREAM_ACCESSIBILITY
     };
     private final int[] STREAM_VOLUME_ALIAS_TELEVISION = new int[] {
         AudioSystem.STREAM_MUSIC,       // STREAM_VOICE_CALL
@@ -320,7 +325,8 @@
         AudioSystem.STREAM_MUSIC,       // STREAM_BLUETOOTH_SCO
         AudioSystem.STREAM_MUSIC,       // STREAM_SYSTEM_ENFORCED
         AudioSystem.STREAM_MUSIC,       // STREAM_DTMF
-        AudioSystem.STREAM_MUSIC        // STREAM_TTS
+        AudioSystem.STREAM_MUSIC,       // STREAM_TTS
+        AudioSystem.STREAM_MUSIC        // STREAM_ACCESSIBILITY
     };
     private final int[] STREAM_VOLUME_ALIAS_DEFAULT = new int[] {
         AudioSystem.STREAM_VOICE_CALL,      // STREAM_VOICE_CALL
@@ -332,7 +338,8 @@
         AudioSystem.STREAM_BLUETOOTH_SCO,   // STREAM_BLUETOOTH_SCO
         AudioSystem.STREAM_RING,            // STREAM_SYSTEM_ENFORCED
         AudioSystem.STREAM_RING,            // STREAM_DTMF
-        AudioSystem.STREAM_MUSIC            // STREAM_TTS
+        AudioSystem.STREAM_MUSIC,           // STREAM_TTS
+        AudioSystem.STREAM_MUSIC            // STREAM_ACCESSIBILITY
     };
     private int[] mStreamVolumeAlias;
 
@@ -351,6 +358,7 @@
         AppOpsManager.OP_AUDIO_MEDIA_VOLUME,            // STREAM_SYSTEM_ENFORCED
         AppOpsManager.OP_AUDIO_MEDIA_VOLUME,            // STREAM_DTMF
         AppOpsManager.OP_AUDIO_MEDIA_VOLUME,            // STREAM_TTS
+        AppOpsManager.OP_AUDIO_ACCESSIBILITY_VOLUME,    // STREAM_ACCESSIBILITY
     };
 
     private final boolean mUseFixedVolume;
@@ -769,7 +777,7 @@
                 TAG,
                 SAFE_VOLUME_CONFIGURE_TIMEOUT_MS);
 
-        StreamOverride.init(mContext);
+        initA11yMonitoring(mContext);
         mControllerService.init();
         onIndicateSystemReady();
     }
@@ -966,6 +974,8 @@
 
     private void updateStreamVolumeAlias(boolean updateVolumes, String caller) {
         int dtmfStreamAlias;
+        final int a11yStreamAlias = sIndependentA11yVolume ?
+                AudioSystem.STREAM_ACCESSIBILITY : AudioSystem.STREAM_MUSIC;
 
         if (mIsSingleVolume) {
             mStreamVolumeAlias = STREAM_VOLUME_ALIAS_TELEVISION;
@@ -994,9 +1004,13 @@
         }
 
         mStreamVolumeAlias[AudioSystem.STREAM_DTMF] = dtmfStreamAlias;
+        mStreamVolumeAlias[AudioSystem.STREAM_ACCESSIBILITY] = a11yStreamAlias;
+
         if (updateVolumes) {
             mStreamStates[AudioSystem.STREAM_DTMF].setAllIndexes(mStreamStates[dtmfStreamAlias],
                     caller);
+            mStreamStates[AudioSystem.STREAM_ACCESSIBILITY].setAllIndexes(
+                    mStreamStates[a11yStreamAlias], caller);
             // apply stream mute states according to new value of mRingerModeAffectedStreams
             setRingerModeInt(getRingerModeInternal(), false);
             sendMsg(mAudioHandler,
@@ -1005,6 +1019,12 @@
                     0,
                     0,
                     mStreamStates[AudioSystem.STREAM_DTMF], 0);
+            sendMsg(mAudioHandler,
+                    MSG_SET_ALL_VOLUMES,
+                    SENDMSG_QUEUE,
+                    0,
+                    0,
+                    mStreamStates[AudioSystem.STREAM_ACCESSIBILITY], 0);
         }
     }
 
@@ -1530,6 +1550,10 @@
 
     private void setStreamVolume(int streamType, int index, int flags, String callingPackage,
             String caller, int uid) {
+        if (DEBUG_VOL) {
+            Log.d(TAG, "setStreamVolume(stream=" + streamType+", index=" + index
+                    + ", calling=" + callingPackage + ")");
+        }
         if (mUseFixedVolume) {
             return;
         }
@@ -3633,7 +3657,7 @@
                     return AudioSystem.STREAM_VOICE_CALL;
                 }
             } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
-                if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) {
+                if (isAfMusicActiveRecently(sStreamOverrideDelayMs)) {
                     if (DEBUG_VOL)
                         Log.v(TAG, "getActiveStreamType: Forcing STREAM_MUSIC stream active");
                     return AudioSystem.STREAM_MUSIC;
@@ -3659,13 +3683,13 @@
                     return AudioSystem.STREAM_VOICE_CALL;
                 }
             } else if (AudioSystem.isStreamActive(AudioSystem.STREAM_NOTIFICATION,
-                    StreamOverride.sDelayMs) ||
+                    sStreamOverrideDelayMs) ||
                     AudioSystem.isStreamActive(AudioSystem.STREAM_RING,
-                            StreamOverride.sDelayMs)) {
+                            sStreamOverrideDelayMs)) {
                 if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: Forcing STREAM_NOTIFICATION");
                 return AudioSystem.STREAM_NOTIFICATION;
             } else if (suggestedStreamType == AudioManager.USE_DEFAULT_STREAM_TYPE) {
-                if (isAfMusicActiveRecently(StreamOverride.sDelayMs)) {
+                if (isAfMusicActiveRecently(sStreamOverrideDelayMs)) {
                     if (DEBUG_VOL) Log.v(TAG, "getActiveStreamType: forcing STREAM_MUSIC");
                     return AudioSystem.STREAM_MUSIC;
                 } else {
@@ -5855,44 +5879,67 @@
     }
 
     //==========================================================================================
-    // Accessibility: taking touch exploration into account for selecting the default
+    // Accessibility
+
+    private void initA11yMonitoring(Context ctxt) {
+        AccessibilityManager accessibilityManager =
+                (AccessibilityManager) ctxt.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        updateDefaultStreamOverrideDelay(accessibilityManager.isTouchExplorationEnabled());
+        updateA11yVolumeAlias(accessibilityManager.isEnabled());
+        accessibilityManager.addTouchExplorationStateChangeListener(this);
+        accessibilityManager.addAccessibilityStateChangeListener(this);
+    }
+
+    //---------------------------------------------------------------------------------
+    // A11y: taking touch exploration into account for selecting the default
     //   stream override timeout when adjusting volume
-    //==========================================================================================
-    private static class StreamOverride
-            implements AccessibilityManager.TouchExplorationStateChangeListener {
+    //---------------------------------------------------------------------------------
 
-        // AudioService.getActiveStreamType() will return:
-        // - STREAM_NOTIFICATION on tablets during this period after a notification stopped
-        // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt
-        // stopped
-        private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 0;
-        private static final int TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS = 1000;
+    // AudioService.getActiveStreamType() will return:
+    // - STREAM_NOTIFICATION on tablets during this period after a notification stopped
+    // - STREAM_MUSIC on phones during this period after music or talkback/voice search prompt
+    // stopped
+    private static final int DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS = 0;
+    private static final int TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS = 1000;
 
-        static int sDelayMs;
+    private static int sStreamOverrideDelayMs;
 
-        static void init(Context ctxt) {
-            AccessibilityManager accessibilityManager =
-                    (AccessibilityManager) ctxt.getSystemService(Context.ACCESSIBILITY_SERVICE);
-            updateDefaultStreamOverrideDelay(
-                    accessibilityManager.isTouchExplorationEnabled());
-            accessibilityManager.addTouchExplorationStateChangeListener(
-                    new StreamOverride());
+    @Override
+    public void onTouchExplorationStateChanged(boolean enabled) {
+        updateDefaultStreamOverrideDelay(enabled);
+    }
+
+    private void updateDefaultStreamOverrideDelay(boolean touchExploreEnabled) {
+        if (touchExploreEnabled) {
+            sStreamOverrideDelayMs = TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS;
+        } else {
+            sStreamOverrideDelayMs = DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS;
         }
+        if (DEBUG_VOL) Log.d(TAG, "Touch exploration enabled=" + touchExploreEnabled
+                + " stream override delay is now " + sStreamOverrideDelayMs + " ms");
+    }
 
-        @Override
-        public void onTouchExplorationStateChanged(boolean enabled) {
-            updateDefaultStreamOverrideDelay(enabled);
-        }
+    //---------------------------------------------------------------------------------
+    // A11y: taking a11y state into account for the handling of a11y prompts volume
+    //---------------------------------------------------------------------------------
 
-        private static void updateDefaultStreamOverrideDelay(boolean touchExploreEnabled) {
-            if (touchExploreEnabled) {
-                sDelayMs = TOUCH_EXPLORE_STREAM_TYPE_OVERRIDE_DELAY_MS;
-            } else {
-                sDelayMs = DEFAULT_STREAM_TYPE_OVERRIDE_DELAY_MS;
-            }
-            if (DEBUG_VOL) Log.d(TAG, "Touch exploration enabled=" + touchExploreEnabled
-                    + " stream override delay is now " + sDelayMs + " ms");
-        }
+    private static boolean sIndependentA11yVolume = false;
+
+    @Override
+    public void onAccessibilityStateChanged(boolean enabled) {
+        updateA11yVolumeAlias(enabled);
+    }
+
+    private void updateA11yVolumeAlias(boolean a11Enabled) {
+        if (DEBUG_VOL) Log.d(TAG, "Accessibility mode changed to " + a11Enabled);
+        // a11y has its own volume stream when a11y service is enabled
+        sIndependentA11yVolume = a11Enabled;
+        // update the volume mapping scheme
+        updateStreamVolumeAlias(true /*updateVolumes*/, TAG);
+        // update the volume controller behavior
+        mVolumeController.setA11yMode(sIndependentA11yVolume ?
+                VolumePolicy.A11Y_MODE_INDEPENDENT_A11Y_VOLUME :
+                    VolumePolicy.A11Y_MODE_MEDIA_A11Y_VOLUME);
     }
 
     //==========================================================================================
@@ -6172,6 +6219,16 @@
                 Log.w(TAG, "Error calling dismiss", e);
             }
         }
+
+        public void setA11yMode(int a11yMode) {
+            if (mController == null)
+                return;
+            try {
+                mController.setA11yMode(a11yMode);
+            } catch (RemoteException e) {
+                Log.w(TAG, "Error calling setA11Mode", e);
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index c73d1dd..aa66917 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -80,7 +80,8 @@
  */
 public class NetworkMonitor extends StateMachine {
     private static final String TAG = NetworkMonitor.class.getSimpleName();
-    private static final boolean DBG = false;
+    private static final boolean DBG  = true;
+    private static final boolean VDBG = false;
 
     // Default configuration values for captive portal detection probes.
     // TODO: append a random length parameter to the default HTTPS url.
@@ -918,7 +919,7 @@
                     latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID());
                     latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID());
                 } else {
-                    if (DBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
+                    if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found");
                     return;
                 }
                 break;
@@ -931,8 +932,8 @@
                     if (cellInfo.isRegistered()) {
                         numRegisteredCellInfo++;
                         if (numRegisteredCellInfo > 1) {
-                            log("more than one registered CellInfo.  Can't " +
-                                    "tell which is active.  Bailing.");
+                            if (VDBG) logw("more than one registered CellInfo." +
+                                    " Can't tell which is active.  Bailing.");
                             return;
                         }
                         if (cellInfo instanceof CellInfoCdma) {
@@ -948,7 +949,7 @@
                             CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity();
                             latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId);
                         } else {
-                            if (DBG) logw("Registered cellinfo is unrecognized");
+                            if (VDBG) logw("Registered cellinfo is unrecognized");
                             return;
                         }
                     }
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index f7b01be..c6bf4c5 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -114,7 +114,7 @@
         }
 
         if (DBG) {
-            Slog.d(TAG, "showNotification " + notifyType
+            Slog.d(TAG, "showNotification id=" + id + " " + notifyType
                     + " transportType=" + getTransportName(transportType)
                     + " extraInfo=" + extraInfo + " highPriority=" + highPriority);
         }
@@ -187,7 +187,7 @@
         try {
             mNotificationManager.notifyAsUser(NOTIFICATION_ID, id, notification, UserHandle.ALL);
         } catch (NullPointerException npe) {
-            Slog.d(TAG, "setNotificationVisible: visible notificationManager npe=" + npe);
+            Slog.d(TAG, "setNotificationVisible: visible notificationManager error", npe);
         }
     }
 
@@ -198,7 +198,7 @@
         try {
             mNotificationManager.cancelAsUser(NOTIFICATION_ID, id, UserHandle.ALL);
         } catch (NullPointerException npe) {
-            Slog.d(TAG, "setNotificationVisible: cancel notificationManager npe=" + npe);
+            Slog.d(TAG, "setNotificationVisible: cancel notificationManager error", npe);
         }
     }
 
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 273bc64..132967c 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -32,11 +32,14 @@
 import android.content.pm.UserInfo;
 import android.hardware.fingerprint.IFingerprintServiceLockoutResetCallback;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.DeadObjectException;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
 import android.os.RemoteException;
 import android.os.SELinux;
 import android.os.ServiceManager;
@@ -625,17 +628,28 @@
 
     private class FingerprintServiceLockoutResetMonitor {
 
+        private static final long WAKELOCK_TIMEOUT_MS = 2000;
         private final IFingerprintServiceLockoutResetCallback mCallback;
+        private final WakeLock mWakeLock;
 
         public FingerprintServiceLockoutResetMonitor(
                 IFingerprintServiceLockoutResetCallback callback) {
             mCallback = callback;
+            mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                    "lockout reset callback");
         }
 
         public void sendLockoutReset() {
             if (mCallback != null) {
                 try {
-                    mCallback.onLockoutReset(mHalDeviceId);
+                    mWakeLock.acquire(WAKELOCK_TIMEOUT_MS);
+                    mCallback.onLockoutReset(mHalDeviceId, new IRemoteCallback.Stub() {
+
+                        @Override
+                        public void sendResult(Bundle data) throws RemoteException {
+                            mWakeLock.release();
+                        }
+                    });
                 } catch (DeadObjectException e) {
                     Slog.w(TAG, "Death object while invoking onLockoutReset: ", e);
                     mHandler.post(mRemoveCallbackRunnable);
@@ -648,6 +662,9 @@
         private final Runnable mRemoveCallbackRunnable = new Runnable() {
             @Override
             public void run() {
+                if (mWakeLock.isHeld()) {
+                    mWakeLock.release();
+                }
                 removeLockoutResetCallback(FingerprintServiceLockoutResetMonitor.this);
             }
         };
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 604b3ed..84c298b 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1837,7 +1837,7 @@
          * @param token The binder for the listener, to check that the caller is allowed
          */
         @Override
-        public void snoozeNotificationFromListener(INotificationListener token, String key,
+        public void snoozeNotificationUntilFromListener(INotificationListener token, String key,
                 long snoozeUntil) {
             long identity = Binder.clearCallingIdentity();
             try {
@@ -1849,6 +1849,38 @@
         }
 
         /**
+         * Allow an INotificationListener to snooze a single notification.
+         *
+         * @param token The binder for the listener, to check that the caller is allowed
+         */
+        @Override
+        public void snoozeNotificationFromListener(INotificationListener token, String key) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                snoozeNotificationInt(key, info);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        /**
+         * Allow an INotificationListener to un-snooze a single notification.
+         *
+         * @param token The binder for the listener, to check that the caller is allowed
+         */
+        @Override
+        public void unsnoozeNotificationFromListener(INotificationListener token, String key) {
+            long identity = Binder.clearCallingIdentity();
+            try {
+                final ManagedServiceInfo info = mListeners.checkServiceTokenLocked(token);
+                unsnoozeNotificationInt(key, info);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        /**
          * Allow an INotificationListener to simulate clearing (dismissing) a single notification.
          *
          * {@see com.android.server.StatusBarManagerService.NotificationCallbacks#onNotificationClear}
@@ -2206,7 +2238,6 @@
 
         @Override
         public ComponentName getEffectsSuppressor() {
-            enforceSystemOrSystemUIOrVolume("INotificationManager.getEffectsSuppressor");
             return !mEffectsSuppressors.isEmpty() ? mEffectsSuppressors.get(0) : null;
         }
 
@@ -3686,6 +3717,34 @@
         }
     }
 
+    void snoozeNotificationInt(String key, ManagedServiceInfo listener) {
+        String listenerName = listener == null ? null : listener.component.toShortString();
+        // TODO: write to event log
+        if (DBG) {
+            Slog.d(TAG, String.format("snooze event(%s, %s)", key, listenerName));
+        }
+        synchronized (mNotificationList) {
+            final NotificationRecord r = mNotificationsByKey.get(key);
+            if (r != null) {
+                mNotificationList.remove(r);
+                cancelNotificationLocked(r, false, REASON_SNOOZED);
+                updateLightsLocked();
+                mSnoozeHelper.snooze(r, r.getUser().getIdentifier());
+                savePolicyFile();
+            }
+        }
+    }
+
+    void unsnoozeNotificationInt(String key, ManagedServiceInfo listener) {
+        String listenerName = listener == null ? null : listener.component.toShortString();
+        // TODO: write to event log
+        if (DBG) {
+            Slog.d(TAG, String.format("unsnooze event(%s, %s)", key, listenerName));
+        }
+        mSnoozeHelper.repost(key, Binder.getCallingUid());
+                savePolicyFile();
+    }
+
     void cancelAllLocked(int callingUid, int callingPid, int userId, int reason,
             ManagedServiceInfo listener, boolean includeCurrentProfiles) {
         String listenerName = listener == null ? null : listener.component.toShortString();
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 738403e..409eef4 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.Uri;
+import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -62,6 +63,8 @@
     // User id : package name : notification key : record.
     private ArrayMap<Integer, ArrayMap<String, ArrayMap<String, NotificationRecord>>>
             mSnoozedNotifications = new ArrayMap<>();
+    // notification key : package.
+    private ArrayMap<String, String> mPackages = new ArrayMap<>();
     private Callback mCallback;
 
     public SnoozeHelper(Context context, Callback callback,
@@ -82,10 +85,20 @@
     }
 
     /**
-     * Records a notification that should be snoozed until the given time and schedules an alarm
-     * to repost at that time.
+     * Snoozes a notification and schedules an alarm to repost at that time.
      */
     protected void snooze(NotificationRecord record, int userId, long until) {
+        snooze(record, userId);
+        scheduleRepost(record.sbn.getPackageName(), record.getKey(), userId, until);
+    }
+
+    /**
+     * Records a snoozed notification.
+     */
+    protected void snooze(NotificationRecord record, int userId) {
+        if (DEBUG) {
+            Slog.d(TAG, "Snoozing " + record.getKey());
+        }
         ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
                 mSnoozedNotifications.get(userId);
         if (records == null) {
@@ -98,13 +111,9 @@
         pkgRecords.put(record.getKey(), record);
         records.put(record.sbn.getPackageName(), pkgRecords);
         mSnoozedNotifications.put(userId, records);
-        if (DEBUG) {
-            Slog.d(TAG, "Snoozing " + record.getKey() + " until " + new Date(until));
-        }
-        scheduleRepost(record.sbn.getPackageName(), record.getKey(), userId, until);
+        mPackages.put(record.getKey(), record.sbn.getPackageName());
     }
 
-
     protected boolean cancel(int userId, String pkg, String tag, int id) {
         if (mSnoozedNotifications.containsKey(userId)) {
             ArrayMap<String, NotificationRecord> recordsForPkg =
@@ -121,6 +130,7 @@
                 if (key != null) {
                     recordsForPkg.remove(key);
                     cancelAlarm(userId, pkg, key);
+                    mPackages.remove(key);
                     return true;
                 }
             }
@@ -145,6 +155,7 @@
                         int P = records.size();
                         for (int k = 0; k < P; k++) {
                             cancelAlarm(userId, snoozedPkgs.keyAt(j), records.keyAt(k));
+                            mPackages.remove(records.keyAt(k));
                         }
                     }
                 }
@@ -162,6 +173,7 @@
                 int N = records.size();
                 for (int i = 0; i < N; i++) {
                     cancelAlarm(userId, pkg, records.keyAt(i));
+                    mPackages.remove(records.keyAt(i));
                 }
                 return true;
             }
@@ -190,8 +202,8 @@
         pkgRecords.put(record.getKey(), record);
     }
 
-    @VisibleForTesting
-    void repost(String pkg, String key, int userId) {
+    protected void repost(String key, int userId) {
+        final String pkg = mPackages.remove(key);
         ArrayMap<String, ArrayMap<String, NotificationRecord>> records =
                 mSnoozedNotifications.get(userId);
         if (records == null) {
@@ -202,6 +214,7 @@
             return;
         }
         final NotificationRecord record = pkgRecords.remove(key);
+
         if (record != null) {
             mCallback.repost(userId, record);
         }
@@ -213,7 +226,6 @@
                 new Intent(REPOST_ACTION)
                         .setData(new Uri.Builder().scheme(REPOST_SCHEME).appendPath(key).build())
                         .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-                        .putExtra(EXTRA_PKG, pkg)
                         .putExtra(EXTRA_KEY, key)
                         .putExtra(EXTRA_USER_ID, userId),
                 PendingIntent.FLAG_UPDATE_CURRENT);
@@ -273,8 +285,8 @@
                 Slog.d(TAG, "Reposting notification");
             }
             if (REPOST_ACTION.equals(intent.getAction())) {
-                repost(intent.getStringExtra(EXTRA_PKG), intent.getStringExtra(EXTRA_KEY),
-                        intent.getIntExtra(EXTRA_USER_ID, 0));
+                repost(intent.getStringExtra(EXTRA_KEY), intent.getIntExtra(EXTRA_USER_ID,
+                        UserHandle.USER_SYSTEM));
             }
         }
     };
diff --git a/services/core/java/com/android/server/pm/EphemeralResolver.java b/services/core/java/com/android/server/pm/EphemeralResolver.java
index 7ddd058..3ce5007 100644
--- a/services/core/java/com/android/server/pm/EphemeralResolver.java
+++ b/services/core/java/com/android/server/pm/EphemeralResolver.java
@@ -46,10 +46,6 @@
 
 /** @hide */
 public abstract class EphemeralResolver {
-
-    /** TODO b/30204367 remove when the platform fully supports ephemeral applications */
-    public static final boolean USE_DEFAULT_EPHEMERAL_LAUNCHER = false;
-
     public static EphemeralResponse doEphemeralResolutionPhaseOne(Context context,
             EphemeralResolverConnection connection, EphemeralRequest requestObj) {
         final Intent intent = requestObj.origIntent;
@@ -162,16 +158,10 @@
                         new IntentSender(failureIntentTarget));
             } catch (RemoteException ignore) { /* ignore; same process */ }
 
-            final Intent ephemeralIntent;
-            if (EphemeralResolver.USE_DEFAULT_EPHEMERAL_LAUNCHER) {
-                // Force the intent to be directed to the ephemeral package
-                ephemeralIntent = new Intent(origIntent);
-                ephemeralIntent.setPackage(ephemeralPackageName);
-            } else {
-                // Success intent goes back to the installer
-                ephemeralIntent = new Intent(launchIntent);
-            }
-
+            // Success intent goes back to the installer
+            final Intent ephemeralIntent = new Intent(launchIntent)
+                    .setComponent(null)
+                    .setPackage(ephemeralPackageName);
             // Intent that is eventually launched if the ephemeral package was
             // installed successfully. This will actually be launched by a platform
             // broadcast receiver.
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 1ef4a9f..acdcc72 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -219,19 +219,20 @@
 
                 final String dexoptType;
                 String oatDir = null;
-                switch (dexoptNeeded) {
+                boolean isOdexLocation = (dexoptNeeded < 0);
+                switch (Math.abs(dexoptNeeded)) {
                     case DexFile.NO_DEXOPT_NEEDED:
                         continue;
-                    case DexFile.DEX2OAT_NEEDED:
+                    case DexFile.DEX2OAT_FROM_SCRATCH:
+                    case DexFile.DEX2OAT_FOR_BOOT_IMAGE:
+                    case DexFile.DEX2OAT_FOR_FILTER:
+                    case DexFile.DEX2OAT_FOR_RELOCATION:
                         dexoptType = "dex2oat";
                         oatDir = createOatDirIfSupported(pkg, dexCodeInstructionSet);
                         break;
-                    case DexFile.PATCHOAT_NEEDED:
+                    case DexFile.PATCHOAT_FOR_RELOCATION:
                         dexoptType = "patchoat";
                         break;
-                    case DexFile.SELF_PATCHOAT_NEEDED:
-                        dexoptType = "self patchoat";
-                        break;
                     default:
                         throw new IllegalStateException("Invalid dexopt:" + dexoptNeeded);
                 }
@@ -383,7 +384,7 @@
         protected int adjustDexoptNeeded(int dexoptNeeded) {
             // Ensure compilation, no matter the current state.
             // TODO: The return value is wrong when patchoat is needed.
-            return DexFile.DEX2OAT_NEEDED;
+            return DexFile.DEX2OAT_FROM_SCRATCH;
         }
     }
 }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index ee3f42b..f5b4b9b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -391,21 +391,19 @@
     static final int SCAN_FORCE_DEX = 1<<2;
     static final int SCAN_UPDATE_SIGNATURE = 1<<3;
     static final int SCAN_NEW_INSTALL = 1<<4;
-    static final int SCAN_NO_PATHS = 1<<5;
-    static final int SCAN_UPDATE_TIME = 1<<6;
-    static final int SCAN_DEFER_DEX = 1<<7;
-    static final int SCAN_BOOTING = 1<<8;
-    static final int SCAN_TRUSTED_OVERLAY = 1<<9;
-    static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<10;
-    static final int SCAN_REPLACING = 1<<11;
-    static final int SCAN_REQUIRE_KNOWN = 1<<12;
-    static final int SCAN_MOVE = 1<<13;
-    static final int SCAN_INITIAL = 1<<14;
-    static final int SCAN_CHECK_ONLY = 1<<15;
-    static final int SCAN_DONT_KILL_APP = 1<<17;
-    static final int SCAN_IGNORE_FROZEN = 1<<18;
-
+    static final int SCAN_UPDATE_TIME = 1<<5;
+    static final int SCAN_BOOTING = 1<<6;
+    static final int SCAN_TRUSTED_OVERLAY = 1<<7;
+    static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<8;
+    static final int SCAN_REPLACING = 1<<9;
+    static final int SCAN_REQUIRE_KNOWN = 1<<10;
+    static final int SCAN_MOVE = 1<<11;
+    static final int SCAN_INITIAL = 1<<12;
+    static final int SCAN_CHECK_ONLY = 1<<13;
+    static final int SCAN_DONT_KILL_APP = 1<<14;
+    static final int SCAN_IGNORE_FROZEN = 1<<15;
     static final int REMOVE_CHATTY = 1<<16;
+    static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<17;
 
     private static final int[] EMPTY_INT_ARRAY = new int[0];
 
@@ -2217,10 +2215,6 @@
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
                     startTime);
 
-            // Set flag to monitor and not change apk file paths when
-            // scanning install directories.
-            final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;
-
             final String bootClassPath = System.getenv("BOOTCLASSPATH");
             final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
 
@@ -2305,6 +2299,14 @@
                 }
             }
 
+            // Set flag to monitor and not change apk file paths when
+            // scanning install directories.
+            int scanFlags = SCAN_BOOTING | SCAN_INITIAL;
+
+            if (mIsUpgrade || mFirstBoot) {
+                scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE;
+            }
+
             // Collect vendor overlay packages. (Do this before scanning any apps.)
             // For security and version matching reason, only consider
             // overlay packages if they reside in the right directory.
@@ -6672,7 +6674,7 @@
         }
     }
 
-    private void scanDirLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
+    private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
         final File[] files = dir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
             Log.d(TAG, "No files in app dir " + dir);
@@ -6683,7 +6685,11 @@
             Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
                     + " flags=0x" + Integer.toHexString(parseFlags));
         }
+        ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
+                mSeparateProcesses, mOnlyCore, mMetrics);
 
+        // Submit files for parsing in parallel
+        int fileCount = 0;
         for (File file : files) {
             final boolean isPackage = (isApkFile(file) || file.isDirectory())
                     && !PackageInstallerService.isStageName(file.getName());
@@ -6691,20 +6697,43 @@
                 // Ignore entries which are not packages
                 continue;
             }
-            try {
-                scanPackageTracedLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
-                        scanFlags, currentTime, null);
-            } catch (PackageManagerException e) {
-                Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());
+            parallelPackageParser.submit(file, parseFlags);
+            fileCount++;
+        }
 
-                // Delete invalid userdata apps
-                if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
-                        e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
-                    logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
-                    removeCodePathLI(file);
+        // Process results one by one
+        for (; fileCount > 0; fileCount--) {
+            ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
+            Throwable throwable = parseResult.throwable;
+            int errorCode = PackageManager.INSTALL_SUCCEEDED;
+
+            if (throwable == null) {
+                try {
+                    scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,
+                            currentTime, null);
+                } catch (PackageManagerException e) {
+                    errorCode = e.error;
+                    Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
                 }
+            } else if (throwable instanceof PackageParser.PackageParserException) {
+                PackageParser.PackageParserException e = (PackageParser.PackageParserException)
+                        throwable;
+                errorCode = e.error;
+                Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
+            } else {
+                throw new IllegalStateException("Unexpected exception occurred while parsing "
+                        + parseResult.scanFile, throwable);
+            }
+
+            // Delete invalid userdata apps
+            if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
+                    errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) {
+                logCriticalInfo(Log.WARN,
+                        "Deleting invalid package at " + parseResult.scanFile);
+                removeCodePathLI(parseResult.scanFile);
             }
         }
+        parallelPackageParser.close();
     }
 
     private static File getSettingsProblemFile() {
@@ -8042,7 +8071,7 @@
 
         applyPolicy(pkg, policyFlags);
 
-        assertPackageIsValid(pkg, policyFlags);
+        assertPackageIsValid(pkg, policyFlags, scanFlags);
 
         // Initialize package source and resource directories
         final File scanFile = new File(pkg.codePath);
@@ -8057,6 +8086,11 @@
         // old setting to restore at the end.
         PackageSetting nonMutatedPs = null;
 
+        // We keep references to the derived CPU Abis from settings in oder to reuse
+        // them in the case where we're not upgrading or booting for the first time.
+        String primaryCpuAbiFromSettings = null;
+        String secondaryCpuAbiFromSettings = null;
+
         // writer
         synchronized (mPackages) {
             if (pkg.mSharedUserId != null) {
@@ -8133,6 +8167,14 @@
                 }
             }
 
+            if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) == 0) {
+                PackageSetting foundPs = mSettings.getPackageLPr(pkg.packageName);
+                if (foundPs != null) {
+                    primaryCpuAbiFromSettings = foundPs.primaryCpuAbiString;
+                    secondaryCpuAbiFromSettings = foundPs.secondaryCpuAbiString;
+                }
+            }
+
             pkgSetting = mSettings.getPackageLPr(pkg.packageName);
             if (pkgSetting != null && pkgSetting.sharedUser != suid) {
                 PackageManagerService.reportSettingsProblem(Log.WARN,
@@ -8165,7 +8207,11 @@
                 }
                 mSettings.addUserToSettingLPw(pkgSetting);
             } else {
-                // REMOVE SharedUserSetting from method; update in a separate call
+                // REMOVE SharedUserSetting from method; update in a separate call.
+                //
+                // TODO(narayan): This update is bogus. nativeLibraryDir & primaryCpuAbi,
+                // secondaryCpuAbi are not known at this point so we always update them
+                // to null here, only to reset them at a later point.
                 Settings.updatePackageSetting(pkgSetting, disabledPkgSetting, suid, destCodeFile,
                         pkg.applicationInfo.nativeLibraryDir, pkg.applicationInfo.primaryCpuAbi,
                         pkg.applicationInfo.secondaryCpuAbi, pkg.applicationInfo.flags,
@@ -8304,18 +8350,34 @@
         final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, pkgSetting);
 
         if ((scanFlags & SCAN_NEW_INSTALL) == 0) {
-            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
-            derivePackageAbi(
-                    pkg, scanFile, cpuAbiOverride, true /*extractLibs*/, mAppLib32InstallDir);
-            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+            if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
+                Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
+                derivePackageAbi(
+                        pkg, scanFile, cpuAbiOverride, true /*extractLibs*/, mAppLib32InstallDir);
+                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
-            // Some system apps still use directory structure for native libraries
-            // in which case we might end up not detecting abi solely based on apk
-            // structure. Try to detect abi based on directory structure.
-            if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() &&
-                    pkg.applicationInfo.primaryCpuAbi == null) {
-                setBundledAppAbisAndRoots(pkg, pkgSetting);
+                // Some system apps still use directory structure for native libraries
+                // in which case we might end up not detecting abi solely based on apk
+                // structure. Try to detect abi based on directory structure.
+                if (isSystemApp(pkg) && !pkg.isUpdatedSystemApp() &&
+                        pkg.applicationInfo.primaryCpuAbi == null) {
+                    setBundledAppAbisAndRoots(pkg, pkgSetting);
+                    setNativeLibraryPaths(pkg, mAppLib32InstallDir);
+                }
+            } else {
+                // This is not a first boot or an upgrade, don't bother deriving the
+                // ABI during the scan. Instead, trust the value that was stored in the
+                // package setting.
+                pkg.applicationInfo.primaryCpuAbi = primaryCpuAbiFromSettings;
+                pkg.applicationInfo.secondaryCpuAbi = secondaryCpuAbiFromSettings;
+
                 setNativeLibraryPaths(pkg, mAppLib32InstallDir);
+
+                if (DEBUG_ABI_SELECTION) {
+                    Slog.i(TAG, "Using ABIS and native lib paths from settings : " +
+                        pkg.packageName + " " + pkg.applicationInfo.primaryCpuAbi + ", " +
+                        pkg.applicationInfo.secondaryCpuAbi);
+                }
             }
         } else {
             if ((scanFlags & SCAN_MOVE) != 0) {
@@ -8488,7 +8550,7 @@
      *
      * @throws PackageManagerException If the package fails any of the validation checks
      */
-    private void assertPackageIsValid(PackageParser.Package pkg, int policyFlags)
+    private void assertPackageIsValid(PackageParser.Package pkg, int policyFlags, int scanFlags)
             throws PackageManagerException {
         if ((policyFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
             assertCodePolicy(pkg);
@@ -8551,7 +8613,7 @@
             // a new system package, we allow the codepath to change from a system location
             // to the user-installed location. If we don't allow this change, any newer,
             // user-installed version of the application will be ignored.
-            if ((policyFlags & SCAN_REQUIRE_KNOWN) != 0) {
+            if ((scanFlags & SCAN_REQUIRE_KNOWN) != 0) {
                 if (mExpectingBetter.containsKey(pkg.packageName)) {
                     logCriticalInfo(Log.WARN,
                             "Relax SCAN_REQUIRE_KNOWN requirement for package " + pkg.packageName);
@@ -8580,7 +8642,7 @@
             // that conflict with existing packages.  Only do this if the
             // package isn't already installed, since we don't want to break
             // things that are installed.
-            if ((policyFlags & SCAN_NEW_INSTALL) != 0) {
+            if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
                 final int N = pkg.providers.size();
                 int i;
                 for (i=0; i<N; i++) {
@@ -9170,11 +9232,6 @@
                                  String cpuAbiOverride, boolean extractLibs,
                                  File appLib32InstallDir)
             throws PackageManagerException {
-        // TODO: We can probably be smarter about this stuff. For installed apps,
-        // we can calculate this information at install time once and for all. For
-        // system apps, we can probably assume that this information doesn't change
-        // after the first boot scan. As things stand, we do lots of unnecessary work.
-
         // Give ourselves some initial paths; we'll come back for another
         // pass once we've determined ABI below.
         setNativeLibraryPaths(pkg, appLib32InstallDir);
@@ -16223,7 +16280,8 @@
 
         final PackageParser.Package newPkg;
         try {
-            newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, SCAN_NO_PATHS, 0, null);
+            newPkg = scanPackageTracedLI(disabledPs.codePath, parseFlags, 0 /* scanFlags */,
+                0 /* currentTime */, null);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": "
                     + e.getMessage());
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 3ad2ae1..6089d2e 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -76,14 +76,12 @@
     String legacyNativeLibraryPathString;
 
     /**
-     * The primary CPU abi for this package. This value is regenerated at every
-     * boot scan.
+     * The primary CPU abi for this package.
      */
     String primaryCpuAbiString;
 
     /**
-     * The secondary CPU abi for this package. This value is regenerated at every
-     * boot scan.
+     * The secondary CPU abi for this package.
      */
     String secondaryCpuAbiString;
 
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
new file mode 100644
index 0000000..158cfc94
--- /dev/null
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.PackageParser;
+import android.os.Process;
+import android.os.Trace;
+import android.util.DisplayMetrics;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
+
+/**
+ * Helper class for parallel parsing of packages using {@link PackageParser}.
+ * <p>Parsing requests are processed by a thread-pool of {@link #MAX_THREADS}.
+ * At any time, at most {@link #QUEUE_CAPACITY} results are kept in RAM</p>
+ */
+class ParallelPackageParser implements AutoCloseable {
+
+    private static final int QUEUE_CAPACITY = 10;
+    private static final int MAX_THREADS = 4;
+
+    private final String[] mSeparateProcesses;
+    private final boolean mOnlyCore;
+    private final DisplayMetrics mMetrics;
+    private volatile String mInterruptedInThread;
+
+    private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
+
+    private final ExecutorService mService = Executors.newFixedThreadPool(MAX_THREADS,
+            new ThreadFactory() {
+                private final AtomicInteger threadNum = new AtomicInteger(0);
+
+                @Override
+                public Thread newThread(final Runnable r) {
+                    return new Thread("package-parsing-thread" + threadNum.incrementAndGet()) {
+                        @Override
+                        public void run() {
+                            Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
+                            r.run();
+                        }
+                    };
+                }
+            });
+
+    ParallelPackageParser(String[] separateProcesses, boolean onlyCoreApps,
+            DisplayMetrics metrics) {
+        mSeparateProcesses = separateProcesses;
+        mOnlyCore = onlyCoreApps;
+        mMetrics = metrics;
+    }
+
+    static class ParseResult {
+
+        PackageParser.Package pkg; // Parsed package
+        File scanFile; // File that was parsed
+        Throwable throwable; // Set if an error occurs during parsing
+
+        @Override
+        public String toString() {
+            return "ParseResult{" +
+                    "pkg=" + pkg +
+                    ", scanFile=" + scanFile +
+                    ", throwable=" + throwable +
+                    '}';
+        }
+    }
+
+    /**
+     * Take the parsed package from the parsing queue, waiting if necessary until the element
+     * appears in the queue.
+     * @return parsed package
+     */
+    public ParseResult take() {
+        try {
+            if (mInterruptedInThread != null) {
+                throw new InterruptedException("Interrupted in " + mInterruptedInThread);
+            }
+            return mQueue.take();
+        } catch (InterruptedException e) {
+            // We cannot recover from interrupt here
+            Thread.currentThread().interrupt();
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * Submits the file for parsing
+     * @param scanFile file to scan
+     * @param parseFlags parse falgs
+     */
+    public void submit(File scanFile, int parseFlags) {
+        mService.submit(() -> {
+            ParseResult pr = new ParseResult();
+            Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
+            try {
+                PackageParser pp = new PackageParser();
+                pp.setSeparateProcesses(mSeparateProcesses);
+                pp.setOnlyCoreApps(mOnlyCore);
+                pp.setDisplayMetrics(mMetrics);
+                pr.scanFile = scanFile;
+                pr.pkg = parsePackage(pp, scanFile, parseFlags);
+            } catch (Throwable e) {
+                pr.throwable = e;
+            } finally {
+                Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
+            }
+            try {
+                mQueue.put(pr);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                // Propagate result to callers of take().
+                // This is helpful to prevent main thread from getting stuck waiting on
+                // ParallelPackageParser to finish in case of interruption
+                mInterruptedInThread = Thread.currentThread().getName();
+            }
+        });
+    }
+
+    @VisibleForTesting
+    protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
+            int parseFlags) throws PackageParser.PackageParserException {
+        return packageParser.parsePackage(scanFile, parseFlags);
+    }
+
+    @Override
+    public void close() {
+        List<Runnable> unfinishedTasks = mService.shutdownNow();
+        if (!unfinishedTasks.isEmpty()) {
+            throw new IllegalStateException("Not all tasks finished before calling close: "
+                    + unfinishedTasks);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index eef8ce2..e330585 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2470,7 +2470,10 @@
 
         final File file = new File(dir, "appid");
         try {
-            FileUtils.stringToFile(file, Integer.toString(ps.appId));
+            // Note that the use of US_ASCII here is safe, we're only writing a decimal
+            // number to the file.
+            FileUtils.bytesToFile(file.getAbsolutePath(),
+                    Integer.toString(ps.appId).getBytes(StandardCharsets.US_ASCII));
             mKernelMapping.put(ps.name, ps.appId);
         } catch (IOException ignored) {
         }
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index cd966ef..841e2a1 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -94,9 +94,6 @@
     public static final String REBOOT_SAFEMODE_PROPERTY = "persist.sys.safemode";
     public static final String RO_SAFEMODE_PROPERTY = "ro.sys.safemode";
 
-    // Indicates whether we should stay in safe mode until ro.build.date.utc is newer than this
-    public static final String AUDIT_SAFEMODE_PROPERTY = "persist.sys.audit_safemode";
-
     // static instance of this thread
     private static final ShutdownThread sInstance = new ShutdownThread();
 
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 47414a0..8cc53f0 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -107,10 +107,17 @@
 public final class TvInputManagerService extends SystemService {
     private static final boolean DEBUG = false;
     private static final String TAG = "TvInputManagerService";
+    private static final String DVB_DIRECTORY = "/dev/dvb";
 
-    // Pattern for selecting the DVB frontend devices from the list of files in the /dev directory.
+    // There are two different formats of DVB frontend devices. One is /dev/dvb%d.frontend%d,
+    // another one is /dev/dvb/adapter%d/frontend%d. Followings are the patterns for selecting the
+    // DVB frontend devices from the list of files in the /dev and /dev/dvb/adapter%d directory.
     private static final Pattern sFrontEndDevicePattern =
             Pattern.compile("^dvb([0-9]+)\\.frontend([0-9]+)$");
+    private static final Pattern sAdapterDirPattern =
+            Pattern.compile("^adapter([0-9]+)$");
+    private static final Pattern sFrontEndInAdapterDirPattern =
+            Pattern.compile("^frontend([0-9]+)$");
 
     private final Context mContext;
     private final TvInputHardwareManager mTvInputHardwareManager;
@@ -1739,17 +1746,46 @@
 
             final long identity = Binder.clearCallingIdentity();
             try {
-                ArrayList<DvbDeviceInfo> deviceInfos = new ArrayList<>();
+                // Pattern1: /dev/dvb%d.frontend%d
+                ArrayList<DvbDeviceInfo> deviceInfosFromPattern1 = new ArrayList<>();
                 File devDirectory = new File("/dev");
+                boolean dvbDirectoryFound = false;
                 for (String fileName : devDirectory.list()) {
                     Matcher matcher = sFrontEndDevicePattern.matcher(fileName);
                     if (matcher.find()) {
                         int adapterId = Integer.parseInt(matcher.group(1));
                         int deviceId = Integer.parseInt(matcher.group(2));
-                        deviceInfos.add(new DvbDeviceInfo(adapterId, deviceId));
+                        deviceInfosFromPattern1.add(new DvbDeviceInfo(adapterId, deviceId));
+                    }
+                    if (TextUtils.equals("dvb", fileName)) {
+                        dvbDirectoryFound = true;
                     }
                 }
-                return Collections.unmodifiableList(deviceInfos);
+                if (!dvbDirectoryFound) {
+                    return Collections.unmodifiableList(deviceInfosFromPattern1);
+                }
+                File dvbDirectory = new File(DVB_DIRECTORY);
+                // Pattern2: /dev/dvb/adapter%d/frontend%d
+                ArrayList<DvbDeviceInfo> deviceInfosFromPattern2 = new ArrayList<>();
+                for (String fileNameInDvb : dvbDirectory.list()) {
+                    Matcher adapterMatcher = sAdapterDirPattern.matcher(fileNameInDvb);
+                    if (adapterMatcher.find()) {
+                        int adapterId = Integer.parseInt(adapterMatcher.group(1));
+                        File adapterDirectory = new File(DVB_DIRECTORY + "/" + fileNameInDvb);
+                        for (String fileNameInAdapter : adapterDirectory.list()) {
+                            Matcher frontendMatcher = sFrontEndInAdapterDirPattern.matcher(
+                                    fileNameInAdapter);
+                            if (frontendMatcher.find()) {
+                                int deviceId = Integer.parseInt(frontendMatcher.group(1));
+                                deviceInfosFromPattern2.add(
+                                        new DvbDeviceInfo(adapterId, deviceId));
+                            }
+                        }
+                    }
+                }
+                return deviceInfosFromPattern2.isEmpty()
+                        ? Collections.unmodifiableList(deviceInfosFromPattern1)
+                        : Collections.unmodifiableList(deviceInfosFromPattern2);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -1763,21 +1799,52 @@
                 throw new SecurityException("Requires DVB_DEVICE permission");
             }
 
+            File devDirectory = new File("/dev");
+            boolean dvbDeviceFound = false;
+            for (String fileName : devDirectory.list()) {
+                if (TextUtils.equals("dvb", fileName)) {
+                    File dvbDirectory = new File(DVB_DIRECTORY);
+                    for (String fileNameInDvb : dvbDirectory.list()) {
+                        Matcher adapterMatcher = sAdapterDirPattern.matcher(fileNameInDvb);
+                        if (adapterMatcher.find()) {
+                            File adapterDirectory = new File(DVB_DIRECTORY + "/" + fileNameInDvb);
+                            for (String fileNameInAdapter : adapterDirectory.list()) {
+                                Matcher frontendMatcher = sFrontEndInAdapterDirPattern.matcher(
+                                        fileNameInAdapter);
+                                if (frontendMatcher.find()) {
+                                    dvbDeviceFound = true;
+                                    break;
+                                }
+                            }
+                        }
+                        if (dvbDeviceFound) {
+                            break;
+                        }
+                    }
+                }
+                if (dvbDeviceFound) {
+                    break;
+                }
+            }
+
             final long identity = Binder.clearCallingIdentity();
             try {
                 String deviceFileName;
                 switch (device) {
                     case TvInputManager.DVB_DEVICE_DEMUX:
-                        deviceFileName = String.format("/dev/dvb%d.demux%d", info.getAdapterId(),
-                                info.getDeviceId());
+                        deviceFileName = String.format(dvbDeviceFound
+                                ? "/dev/dvb/adapter%d/demux%d" : "/dev/dvb%d.demux%d",
+                                info.getAdapterId(), info.getDeviceId());
                         break;
                     case TvInputManager.DVB_DEVICE_DVR:
-                        deviceFileName = String.format("/dev/dvb%d.dvr%d", info.getAdapterId(),
-                                info.getDeviceId());
+                        deviceFileName = String.format(dvbDeviceFound
+                                ? "/dev/dvb/adapter%d/dvr%d" : "/dev/dvb%d.dvr%d",
+                                info.getAdapterId(), info.getDeviceId());
                         break;
                     case TvInputManager.DVB_DEVICE_FRONTEND:
-                        deviceFileName = String.format("/dev/dvb%d.frontend%d", info.getAdapterId(),
-                                info.getDeviceId());
+                        deviceFileName = String.format(dvbDeviceFound
+                                ? "/dev/dvb/adapter%d/frontend%d" : "/dev/dvb%d.frontend%d",
+                                info.getAdapterId(), info.getDeviceId());
                         break;
                     default:
                         throw new IllegalArgumentException("Invalid DVB device: " + device);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 50b0f38..f51b6a0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -26,9 +26,6 @@
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -333,8 +330,6 @@
 
     private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f;
 
-    private static final String PROPERTY_BUILD_DATE_UTC = "ro.build.date.utc";
-
     // Enums for animation scale update types.
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({WINDOW_ANIMATION_SCALE, TRANSITION_ANIMATION_SCALE, ANIMATION_DURATION_SCALE})
@@ -5969,35 +5964,6 @@
                 mFocusedApp.mTask.mStack : null;
     }
 
-    private void showAuditSafeModeNotification() {
-        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
-                new Intent(Intent.ACTION_VIEW,
-                           Uri.parse("https://support.google.com/nexus/answer/2852139")), 0);
-
-        String title = mContext.getString(R.string.audit_safemode_notification);
-
-        Notification notification = new Notification.Builder(mContext)
-                .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
-                .setWhen(0)
-                .setOngoing(true)
-                .setTicker(title)
-                .setLocalOnly(true)
-                .setPriority(Notification.PRIORITY_HIGH)
-                .setVisibility(Notification.VISIBILITY_PUBLIC)
-                .setColor(mContext.getColor(
-                        com.android.internal.R.color.system_notification_accent_color))
-                .setContentTitle(title)
-                .setContentText(mContext.getString(R.string.audit_safemode_notification_details))
-                .setContentIntent(pendingIntent)
-                .build();
-
-        NotificationManager notificationManager = (NotificationManager) mContext
-                .getSystemService(Context.NOTIFICATION_SERVICE);
-
-        notificationManager.notifyAsUser(null, R.string.audit_safemode_notification, notification,
-                UserHandle.ALL);
-    }
-
     public boolean detectSafeMode() {
         if (!mInputMonitor.waitForInputDevicesReady(
                 INPUT_DEVICES_READY_FOR_SAFE_MODE_DETECTION_TIMEOUT_MILLIS)) {
@@ -6025,23 +5991,8 @@
         try {
             if (SystemProperties.getInt(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, 0) != 0
                     || SystemProperties.getInt(ShutdownThread.RO_SAFEMODE_PROPERTY, 0) != 0) {
-                int auditSafeMode = SystemProperties.getInt(ShutdownThread.AUDIT_SAFEMODE_PROPERTY, 0);
-
-                if (auditSafeMode == 0) {
-                    mSafeMode = true;
-                    SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, "");
-                } else {
-                    // stay in safe mode until we have updated to a newer build
-                    int buildDate = SystemProperties.getInt(PROPERTY_BUILD_DATE_UTC, 0);
-
-                    if (auditSafeMode >= buildDate) {
-                        mSafeMode = true;
-                        showAuditSafeModeNotification();
-                    } else {
-                        SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, "");
-                        SystemProperties.set(ShutdownThread.AUDIT_SAFEMODE_PROPERTY, "");
-                    }
-                }
+                mSafeMode = true;
+                SystemProperties.set(ShutdownThread.REBOOT_SAFEMODE_PROPERTY, "");
             }
         } catch (IllegalArgumentException e) {
         }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 661df9cd..59c9102 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -546,17 +546,22 @@
      * Compares two window sub-layers and returns -1 if the first is lesser than the second in terms
      * of z-order and 1 otherwise.
      */
-    private static final Comparator<WindowState> sWindowSubLayerComparator = (w1, w2) -> {
-        final int layer1 = w1.mSubLayer;
-        final int layer2 = w2.mSubLayer;
-        if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
-            // We insert the child window into the list ordered by the sub-layer.
-            // For same sub-layers, the negative one should go below others; the positive one should
-            // go above others.
-            return -1;
-        }
-        return 1;
-    };
+    private static final Comparator<WindowState> sWindowSubLayerComparator =
+            new Comparator<WindowState>() {
+                @Override
+                public int compare(WindowState w1, WindowState w2) {
+                    final int layer1 = w1.mSubLayer;
+                    final int layer2 = w2.mSubLayer;
+                    if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
+                        // We insert the child window into the list ordered by
+                        // the sub-layer.  For same sub-layers, the negative one
+                        // should go below others; the positive one should go
+                        // above others.
+                        return -1;
+                    }
+                    return 1;
+                };
+            };
 
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 4d43e8e..ac0e622 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -46,6 +46,7 @@
 LOCAL_SHARED_LIBRARIES += \
     libandroid_runtime \
     libandroidfw \
+    libbase \
     libappfuse \
     libbinder \
     libcutils \
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c497cb1..895497c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -544,8 +544,10 @@
                 new MonitoringCertNotificationTask().execute(userId);
             }
             if (Intent.ACTION_USER_ADDED.equals(action)) {
+                sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_ADDED, userHandle);
                 disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
             } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                sendUserAddedOrRemovedCommand(DeviceAdminReceiver.ACTION_USER_REMOVED, userHandle);
                 disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
                 removeUserData(userHandle);
             } else if (Intent.ACTION_USER_STARTED.equals(action)) {
@@ -568,6 +570,17 @@
                 clearWipeProfileNotification();
             }
         }
+
+        private void sendUserAddedOrRemovedCommand(String action, int userHandle) {
+            synchronized (DevicePolicyManagerService.this) {
+                ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+                if (deviceOwner != null) {
+                    Bundle extras = new Bundle();
+                    extras.putParcelable(Intent.EXTRA_USER, UserHandle.of(userHandle));
+                    sendAdminCommandLocked(deviceOwner, action, extras, null);
+                }
+            }
+        }
     };
 
     static class ActiveAdmin {
@@ -6092,18 +6105,23 @@
     }
 
     private void clearDeviceOwnerLocked(ActiveAdmin admin, int userId) {
-        disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
         if (admin != null) {
             admin.disableCamera = false;
             admin.userRestrictions = null;
             admin.forceEphemeralUsers = false;
             mUserManagerInternal.setForceEphemeralUsers(admin.forceEphemeralUsers);
+            final DevicePolicyData policyData = getUserData(UserHandle.USER_SYSTEM);
+            policyData.mLastSecurityLogRetrievalTime = -1;
+            policyData.mLastBugReportRequestTime = -1;
+            policyData.mLastNetworkLogsRetrievalTime = -1;
+            saveSettingsLocked(UserHandle.USER_SYSTEM);
         }
         clearUserPoliciesLocked(userId);
 
         mOwners.clearDeviceOwner();
         mOwners.writeDeviceOwner();
         updateDeviceOwnerLocked();
+        disableDeviceOwnerManagedSingleUserFeaturesIfNeeded();
         try {
             if (mInjector.getIBackupManager() != null) {
                 // Reactivate backup service.
@@ -6581,10 +6599,14 @@
         }
     }
 
-    private void enforceSystemUid() {
-        if (!isCallerWithSystemUid()) {
-            throw new SecurityException("Only the system can call this method.");
+    private void enforceDeviceOwnerOrManageUsers() {
+        synchronized (this) {
+            if (getActiveAdminWithPolicyForUidLocked(null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER,
+                    mInjector.binderGetCallingUid()) != null) {
+                return;
+            }
         }
+        enforceManageUsers();
     }
 
     private void ensureCallerPackage(@Nullable String packageName) {
@@ -6634,7 +6656,8 @@
     }
 
     private boolean isManagedProfile(int userHandle) {
-        return getUserInfo(userHandle).isManagedProfile();
+        final UserInfo user = getUserInfo(userHandle);
+        return user != null && user.isManagedProfile();
     }
 
     private void enableIfNecessary(String packageName, int userId) {
@@ -8903,9 +8926,7 @@
         synchronized (this) {
             getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
         }
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
-        final UserInfo user = getUserInfo(callingUserId);
-        return user != null && user.isManagedProfile();
+        return isManagedProfile(mInjector.userHandleGetCallingUserId());
     }
 
     @Override
@@ -9185,19 +9206,34 @@
     }
 
     private synchronized void disableDeviceOwnerManagedSingleUserFeaturesIfNeeded() {
-        if (!mOwners.hasDeviceOwner()) {
-            return;
-        }
-        if (!isDeviceOwnerManagedSingleUserDevice()) {
+        final boolean isSingleUserManagedDevice = isDeviceOwnerManagedSingleUserDevice();
+
+        // disable security logging if needed
+        if (!isSingleUserManagedDevice) {
             mInjector.securityLogSetLoggingEnabledProperty(false);
+            Slog.w(LOG_TAG, "Security logging turned off as it's no longer a single user managed"
+                    + " device.");
+        }
 
-            getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = false;
-            saveSettingsLocked(mInjector.userHandleGetCallingUserId());
-            setNetworkLoggingActiveInternal(false);
-
+        // disable backup service if needed
+        // note: when clearing DO, the backup service shouldn't be disabled if it was enabled by
+        // the device owner
+        if (mOwners.hasDeviceOwner() && !isSingleUserManagedDevice) {
             setBackupServiceEnabledInternal(false);
-            Slog.w(LOG_TAG, "Security logging, network logging and backup service turned off as"
-                    + " it's not a single user device.");
+            Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user.");
+        }
+
+        // disable network logging if needed
+        if (!isSingleUserManagedDevice) {
+            setNetworkLoggingActiveInternal(false);
+            Slog.w(LOG_TAG, "Network logging turned off as it's no longer a single user managed"
+                    + " device.");
+            // if there still is a device owner, disable logging policy, otherwise the admin
+            // has been nuked
+            if (mOwners.hasDeviceOwner()) {
+                getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = false;
+                saveSettingsLocked(mOwners.getDeviceOwnerUserId());
+            }
         }
     }
 
@@ -9522,65 +9558,97 @@
         Preconditions.checkNotNull(caller);
         Preconditions.checkNotNull(serviceIntent);
         Preconditions.checkNotNull(connection);
-        final int callingUserId = mInjector.userHandleGetCallingUserId();
-        Preconditions.checkArgument(callingUserId != targetUserId,
+        Preconditions.checkArgument(mInjector.userHandleGetCallingUserId() != targetUserId,
                 "target user id must be different from the calling user id");
 
-        synchronized (this) {
-            final ActiveAdmin callingAdmin = getActiveAdminForCallerLocked(admin,
-                    DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
-            // Ensure the target user is valid.
-            if (isDeviceOwner(callingAdmin)) {
-                enforceManagedProfile(targetUserId, "Target user must be a managed profile");
-            } else {
-                // Further lock down to profile owner in managed profile.
-                enforceManagedProfile(callingUserId,
-                        "Only support profile owner in managed profile.");
-                if (mOwners.getDeviceOwnerUserId() != targetUserId) {
-                    throw new SecurityException("Target user must be a device owner.");
-                }
-            }
+        if (!getBindDeviceAdminTargetUsers(admin).contains(UserHandle.of(targetUserId))) {
+            throw new SecurityException("Not allowed to bind to target user id");
         }
+
+        final String targetPackage;
+        synchronized (this) {
+            targetPackage = getOwnerPackageNameForUserLocked(targetUserId);
+        }
+
         final long callingIdentity = mInjector.binderClearCallingIdentity();
         try {
-            if (!mUserManager.isSameProfileGroup(callingUserId, targetUserId)) {
-                throw new SecurityException(
-                        "Can only bind service across users under the same profile group");
-            }
-            final String targetPackage;
-            synchronized (this) {
-                targetPackage = getOwnerPackageNameForUserLocked(targetUserId);
-            }
-            // STOPSHIP(b/31952368): Add policy to control which packages can talk.
-            if (TextUtils.isEmpty(targetPackage) || !targetPackage.equals(admin.getPackageName())) {
-                throw new SecurityException("Device owner and profile owner must be the same " +
-                        "package in order to communicate.");
-            }
             // Validate and sanitize the incoming service intent.
             final Intent sanitizedIntent =
-                    createCrossUserServiceIntent(serviceIntent, targetPackage);
+                    createCrossUserServiceIntent(serviceIntent, targetPackage, targetUserId);
             if (sanitizedIntent == null) {
                 // Fail, cannot lookup the target service.
                 throw new SecurityException("Invalid intent or failed to look up the service");
             }
+
             // Ask ActivityManager to bind it. Notice that we are binding the service with the
             // caller app instead of DevicePolicyManagerService.
-            try {
-                return mInjector.getIActivityManager().bindService(
-                        caller, activtiyToken, serviceIntent,
-                        serviceIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                        connection, flags, mContext.getOpPackageName(),
-                        targetUserId) != 0;
-            } catch (RemoteException ex) {
-                // Same process, should not happen.
-            }
+            return mInjector.getIActivityManager().bindService(
+                    caller, activtiyToken, serviceIntent,
+                    serviceIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    connection, flags, mContext.getOpPackageName(),
+                    targetUserId) != 0;
+        } catch (RemoteException ex) {
+            // Same process, should not happen.
         } finally {
             mInjector.binderRestoreCallingIdentity(callingIdentity);
         }
-        // Fail to bind.
+
+        // Failed to bind.
         return false;
     }
 
+    @Override
+    public @NonNull List<UserHandle> getBindDeviceAdminTargetUsers(@NonNull ComponentName admin) {
+        if (!mHasFeature) {
+            return Collections.emptyList();
+        }
+        Preconditions.checkNotNull(admin);
+        ArrayList<UserHandle> targetUsers = new ArrayList<>();
+
+        synchronized (this) {
+            ActiveAdmin callingOwner = getActiveAdminForCallerLocked(
+                    admin, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+
+            final int callingUserId = mInjector.userHandleGetCallingUserId();
+            final boolean isCallerDeviceOwner = isDeviceOwner(callingOwner);
+            final boolean isCallerManagedProfile = isManagedProfile(callingUserId);
+            if (!isCallerDeviceOwner && !isCallerManagedProfile
+                    /* STOPSHIP(b/32326223) Reinstate when setAffiliationIds is public
+                    ||   !isAffiliatedUser(callingUserId) */) {
+                return targetUsers;
+            }
+
+            final long callingIdentity = mInjector.binderClearCallingIdentity();
+            try {
+                String callingOwnerPackage = callingOwner.info.getComponent().getPackageName();
+                for (int userId : mUserManager.getProfileIds(
+                        callingUserId, /* enabledOnly= */ false)) {
+                    if (userId == callingUserId) {
+                        continue;
+                    }
+
+                    // We only allow the device owner and a managed profile owner to bind to each
+                    // other.
+                    if ((isCallerManagedProfile && userId == mOwners.getDeviceOwnerUserId())
+                            || (isCallerDeviceOwner && isManagedProfile(userId))) {
+                        String targetOwnerPackage = getOwnerPackageNameForUserLocked(userId);
+
+                        // Both must be the same package and be affiliated in order to bind.
+                        if (callingOwnerPackage.equals(targetOwnerPackage)
+                            /* STOPSHIP(b/32326223) Reinstate when setAffiliationIds is public
+                               && isAffiliatedUser(userId)*/) {
+                            targetUsers.add(UserHandle.of(userId));
+                        }
+                    }
+                }
+            } finally {
+                mInjector.binderRestoreCallingIdentity(callingIdentity);
+            }
+        }
+
+        return targetUsers;
+    }
+
     /**
      * Return true if a given user has any accounts that'll prevent installing a device or profile
      * owner {@code owner}.
@@ -9761,7 +9829,7 @@
      * Return the package name of owner in a given user.
      */
     private String getOwnerPackageNameForUserLocked(int userId) {
-        return getDeviceOwnerUserId() == userId
+        return mOwners.getDeviceOwnerUserId() == userId
                 ? mOwners.getDeviceOwnerPackageName()
                 : mOwners.getProfileOwnerPackage(userId);
     }
@@ -9772,14 +9840,19 @@
      * @return Intent that have component explicitly set. {@code null} if the incoming intent
      *         or target service is invalid.
      */
-    private Intent createCrossUserServiceIntent (
-            @NonNull Intent rawIntent, @NonNull String expectedPackageName) {
+    private Intent createCrossUserServiceIntent(
+            @NonNull Intent rawIntent, @NonNull String expectedPackageName,
+            @UserIdInt int targetUserId) throws RemoteException {
         if (rawIntent.getComponent() == null && rawIntent.getPackage() == null) {
             Log.e(LOG_TAG, "Service intent must be explicit (with a package name or component): "
                     + rawIntent);
             return null;
         }
-        ResolveInfo info = mInjector.getPackageManager().resolveService(rawIntent, 0);
+        ResolveInfo info = mIPackageManager.resolveService(
+                rawIntent,
+                rawIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                0,  // flags
+                targetUserId);
         if (info == null || info.serviceInfo == null) {
             Log.e(LOG_TAG, "Fail to look up the service: " + rawIntent);
             return null;
@@ -9801,19 +9874,19 @@
 
     @Override
     public long getLastSecurityLogRetrievalTime() {
-        enforceSystemUid();
+        enforceDeviceOwnerOrManageUsers();
         return getUserData(UserHandle.USER_SYSTEM).mLastSecurityLogRetrievalTime;
      }
 
     @Override
     public long getLastBugReportRequestTime() {
-        enforceSystemUid();
+        enforceDeviceOwnerOrManageUsers();
         return getUserData(UserHandle.USER_SYSTEM).mLastBugReportRequestTime;
      }
 
     @Override
     public long getLastNetworkLogRetrievalTime() {
-        enforceSystemUid();
+        enforceDeviceOwnerOrManageUsers();
         return getUserData(UserHandle.USER_SYSTEM).mLastNetworkLogsRetrievalTime;
     }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 29cc43f..957d4c5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -106,6 +106,8 @@
             Bundle extras = new Bundle();
             extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken);
             extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size());
+            Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
+                    + mCurrentFullBatchToken);
             mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
         } else {
             mFullBatch = null;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index ba23f21..1fc4378 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -41,12 +41,10 @@
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
-import android.os.UserManager;
 import android.os.storage.IStorageManager;
-import android.provider.Settings;
+import android.util.BootTimingsTraceLog;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
-import android.util.Pair;
 import android.util.Slog;
 import android.view.WindowManager;
 
@@ -55,7 +53,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.os.BinderInternal;
 import com.android.internal.os.SamplingProfilerIntegration;
-import com.android.internal.os.ZygoteInit;
 import com.android.internal.policy.EmergencyAffordanceManager;
 import com.android.internal.widget.ILockSettings;
 import com.android.server.accessibility.AccessibilityManagerService;
@@ -102,8 +99,8 @@
 import com.android.server.storage.DeviceStorageMonitorService;
 import com.android.server.telecom.TelecomLoaderService;
 import com.android.server.trust.TrustManagerService;
-import com.android.server.tv.TvRemoteService;
 import com.android.server.tv.TvInputManagerService;
+import com.android.server.tv.TvRemoteService;
 import com.android.server.twilight.TwilightService;
 import com.android.server.usage.UsageStatsService;
 import com.android.server.vr.VrManagerService;
@@ -112,8 +109,6 @@
 
 import dalvik.system.VMRuntime;
 
-import java.util.ArrayDeque;
-import java.util.Deque;
 import java.io.File;
 import java.io.IOException;
 import java.util.Locale;
@@ -125,11 +120,8 @@
 public final class SystemServer {
     private static final String TAG = "SystemServer";
 
-    private static final boolean LOG_BOOT_TIME = true;
-    // Debug boot time for every step if it's non-user build.
-    private static final boolean DEBUG_BOOT_TIME = LOG_BOOT_TIME && !"user".equals(Build.TYPE);
-    private static final String TAG_BOOT_TIME = "SystemServerTiming";
-    private static final Deque<Pair<String, Long>> START_TIMES = new ArrayDeque<>();
+    private static final BootTimingsTraceLog BOOT_TIMINGS_TRACE_LOG
+            = new BootTimingsTraceLog("SystemServerTiming", Trace.TRACE_TAG_SYSTEM_SERVER);
 
     private static final String ENCRYPTING_STATE = "trigger_restart_min_framework";
     private static final String ENCRYPTED_STATE = "1";
@@ -279,6 +271,11 @@
             int uptimeMillis = (int) SystemClock.uptimeMillis();
             EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
             MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis);
+            // Also report when first stage of init has started
+            long initStartNs = SystemProperties.getLong("init.start", -1);
+            if (initStartNs >= 0) {
+                MetricsLogger.histogram(null, "boot_android_init", (int)(initStartNs / 1000000));
+            }
 
             // In case the runtime switched since last boot (such as when
             // the old runtime was removed in an OTA), set the system
@@ -1643,24 +1640,11 @@
     }
 
     private static void traceBeginAndSlog(String name) {
-        Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, name);
         Slog.i(TAG, name);
-        if (DEBUG_BOOT_TIME) {
-            START_TIMES.push(Pair.create(name, Long.valueOf(SystemClock.elapsedRealtime())));
-        }
+        BOOT_TIMINGS_TRACE_LOG.traceBegin(name);
     }
 
     private static void traceEnd() {
-        Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
-        if (!DEBUG_BOOT_TIME) {
-            return;
-        }
-        Pair<String, Long> event = START_TIMES.pollFirst();
-        if (event == null) {
-            Slog.w(TAG, "traceEnd called more times than traceBeginAndSlog");
-            return;
-        }
-        Slog.d(TAG_BOOT_TIME, event.first + " took to complete: "
-                + (SystemClock.elapsedRealtime() - event.second) + "ms");
+        BOOT_TIMINGS_TRACE_LOG.traceEnd();
     }
 }
diff --git a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
index 7a3ee7f..4f6a35c 100644
--- a/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/SnoozeHelperTest.java
@@ -37,6 +37,7 @@
 import static junit.framework.Assert.assertTrue;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -65,7 +66,7 @@
     }
 
     @Test
-    public void testSnooze() throws Exception {
+    public void testSnoozeForTime() throws Exception {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM, 1000);
         verify(mAm, times(1)).setExactAndAllowWhileIdle(
@@ -75,6 +76,16 @@
     }
 
     @Test
+    public void testSnooze() throws Exception {
+        NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+        mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM);
+        verify(mAm, never()).setExactAndAllowWhileIdle(
+                anyInt(), anyLong(), any(PendingIntent.class));
+        assertTrue(mSnoozeHelper.isSnoozed(
+                UserHandle.USER_SYSTEM, r.sbn.getPackageName(), r.getKey()));
+    }
+
+    @Test
     public void testCancelByApp() throws Exception {
         NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
         NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
@@ -152,7 +163,7 @@
         mSnoozeHelper.snooze(r , UserHandle.USER_SYSTEM, 1000);
         NotificationRecord r2 = getNotificationRecord("pkg", 2, "one", UserHandle.ALL);
         mSnoozeHelper.snooze(r2 , UserHandle.USER_ALL, 1000);
-        mSnoozeHelper.repost(r.sbn.getPackageName(), r.getKey(), UserHandle.USER_SYSTEM);
+        mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM);
         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r);
     }
 
@@ -165,7 +176,7 @@
         mSnoozeHelper.update(UserHandle.USER_SYSTEM, r);
         verify(mCallback, never()).repost(anyInt(), any(NotificationRecord.class));
 
-        mSnoozeHelper.repost(r.sbn.getPackageName(), r.getKey(), UserHandle.USER_SYSTEM);
+        mSnoozeHelper.repost(r.getKey(), UserHandle.USER_SYSTEM);
         verify(mCallback, times(1)).repost(UserHandle.USER_SYSTEM, r);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
index 5b565a7..8591dae 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountsDbTest.java
@@ -37,6 +37,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 /**
@@ -338,4 +339,65 @@
         actualId = mAccountsDb.findDeAccountId(account);
         assertEquals(-1, actualId);
     }
+
+    @Test
+    public void testFindDeAccountByAccountId() {
+        long accId = 10;
+        Account account = new Account("name", "example.com");
+        assertNull(mAccountsDb.findDeAccountByAccountId(accId));
+
+        mAccountsDb.insertDeAccount(account, accId);
+
+        Account foundAccount = mAccountsDb.findDeAccountByAccountId(accId);
+        assertEquals(account, foundAccount);
+    }
+
+    @Test
+    public void testVisibilityFindSetDelete() {
+        long accId = 10;
+        int uid1 = 100500;
+        int uid2 = 100501;
+        Account account = new Account("name", "example.com");
+        assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+
+        mAccountsDb.insertDeAccount(account, accId);
+        assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+        assertNull(mAccountsDb.findAccountVisibility(accId, uid1));
+
+        mAccountsDb.setAccountVisibility(accId, uid1, 1);
+        assertEquals(mAccountsDb.findAccountVisibility(account, uid1), Integer.valueOf(1));
+        assertEquals(mAccountsDb.findAccountVisibility(accId, uid1), Integer.valueOf(1));
+
+        mAccountsDb.setAccountVisibility(accId, uid2, 2);
+        assertEquals(mAccountsDb.findAccountVisibility(accId, uid2), Integer.valueOf(2));
+
+        mAccountsDb.setAccountVisibility(accId, uid2, 3);
+        assertEquals(mAccountsDb.findAccountVisibility(accId, uid2), Integer.valueOf(3));
+
+        Map<Integer, Integer> vis = mAccountsDb.findAccountVisibilityForAccountId(accId);
+        assertEquals(vis.size(), 2);
+        assertEquals(vis.get(uid1), Integer.valueOf(1));
+        assertEquals(vis.get(uid2), Integer.valueOf(3));
+
+        assertTrue(mAccountsDb.deleteAccountVisibilityForUid(uid1));
+        assertNull(mAccountsDb.findAccountVisibility(accId, uid1));
+        assertFalse(mAccountsDb.deleteAccountVisibilityForUid(uid1)); // Already deleted.
+    }
+
+    @Test
+    public void testVisibilityCleanupTrigger() {
+        long accId = 10;
+        int uid1 = 100500;
+        Account account = new Account("name", "example.com");
+
+        assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+        mAccountsDb.insertDeAccount(account, accId);
+        assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+
+        mAccountsDb.setAccountVisibility(accId, uid1, 1);
+        assertEquals(mAccountsDb.findAccountVisibility(accId, uid1), Integer.valueOf(1));
+
+        assertTrue(mAccountsDb.deleteDeAccount(accId)); // Trigger should remove visibility.
+        assertNull(mAccountsDb.findAccountVisibility(account, uid1));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index e55cafb..cff5b41 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -2273,11 +2273,13 @@
         assertFalse(dpms.hasUserSetupCompleted());
     }
 
-    private long getLastSecurityLogRetrievalTime() {
+    private void clearDeviceOwner() throws Exception {
         final long ident = mContext.binder.clearCallingIdentity();
-        final long lastSecurityLogRetrievalTime = dpm.getLastSecurityLogRetrievalTime();
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        doReturn(DpmMockContext.CALLER_SYSTEM_USER_UID).when(mContext.packageManager)
+                .getPackageUidAsUser(eq(admin1.getPackageName()), anyInt());
+        dpm.clearDeviceOwnerApp(admin1.getPackageName());
         mContext.binder.restoreCallingIdentity(ident);
-        return lastSecurityLogRetrievalTime;
     }
 
     public void testGetLastSecurityLogRetrievalTime() throws Exception {
@@ -2288,16 +2290,16 @@
                 .thenReturn(true);
 
         // No logs were retrieved so far.
-        assertEquals(-1, getLastSecurityLogRetrievalTime());
+        assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
 
         // Enabling logging should not change the timestamp.
         dpm.setSecurityLoggingEnabled(admin1, true);
-        assertEquals(-1, getLastSecurityLogRetrievalTime());
+        assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
 
         // Retrieving the logs should update the timestamp.
         final long beforeRetrieval = System.currentTimeMillis();
         dpm.retrieveSecurityLogs(admin1);
-        final long firstSecurityLogRetrievalTime = getLastSecurityLogRetrievalTime();
+        final long firstSecurityLogRetrievalTime = dpm.getLastSecurityLogRetrievalTime();
         final long afterRetrieval = System.currentTimeMillis();
         assertTrue(firstSecurityLogRetrievalTime >= beforeRetrieval);
         assertTrue(firstSecurityLogRetrievalTime <= afterRetrieval);
@@ -2305,33 +2307,40 @@
         // Retrieving the pre-boot logs should update the timestamp.
         Thread.sleep(2);
         dpm.retrievePreRebootSecurityLogs(admin1);
-        final long secondSecurityLogRetrievalTime = getLastSecurityLogRetrievalTime();
+        final long secondSecurityLogRetrievalTime = dpm.getLastSecurityLogRetrievalTime();
         assertTrue(secondSecurityLogRetrievalTime > firstSecurityLogRetrievalTime);
 
         // Checking the timestamp again should not change it.
         Thread.sleep(2);
-        assertEquals(secondSecurityLogRetrievalTime, getLastSecurityLogRetrievalTime());
+        assertEquals(secondSecurityLogRetrievalTime, dpm.getLastSecurityLogRetrievalTime());
 
         // Retrieving the logs again should update the timestamp.
         dpm.retrieveSecurityLogs(admin1);
-        final long thirdSecurityLogRetrievalTime = getLastSecurityLogRetrievalTime();
+        final long thirdSecurityLogRetrievalTime = dpm.getLastSecurityLogRetrievalTime();
         assertTrue(thirdSecurityLogRetrievalTime > secondSecurityLogRetrievalTime);
 
         // Disabling logging should not change the timestamp.
         Thread.sleep(2);
         dpm.setSecurityLoggingEnabled(admin1, false);
-        assertEquals(thirdSecurityLogRetrievalTime, getLastSecurityLogRetrievalTime());
+        assertEquals(thirdSecurityLogRetrievalTime, dpm.getLastSecurityLogRetrievalTime());
 
         // Restarting the DPMS should not lose the timestamp.
         initializeDpms();
-        assertEquals(thirdSecurityLogRetrievalTime, getLastSecurityLogRetrievalTime());
-    }
+        assertEquals(thirdSecurityLogRetrievalTime, dpm.getLastSecurityLogRetrievalTime());
 
-    private long getLastBugReportRequestTime() {
-        final long ident = mContext.binder.clearCallingIdentity();
-        final long lastBugRequestTime = dpm.getLastBugReportRequestTime();
-        mContext.binder.restoreCallingIdentity(ident);
-        return lastBugRequestTime;
+        // Any uid holding MANAGE_USERS permission can retrieve the timestamp.
+        mContext.binder.callingUid = 1234567;
+        mContext.callerPermissions.add(permission.MANAGE_USERS);
+        assertEquals(thirdSecurityLogRetrievalTime, dpm.getLastSecurityLogRetrievalTime());
+        mContext.callerPermissions.remove(permission.MANAGE_USERS);
+
+        // System can retrieve the timestamp.
+        mContext.binder.clearCallingIdentity();
+        assertEquals(thirdSecurityLogRetrievalTime, dpm.getLastSecurityLogRetrievalTime());
+
+        // Removing the device owner should clear the timestamp.
+        clearDeviceOwner();
+        assertEquals(-1, dpm.getLastSecurityLogRetrievalTime());
     }
 
     public void testGetLastBugReportRequestTime() throws Exception {
@@ -2346,30 +2355,37 @@
                 anyObject())).thenReturn(Color.WHITE);
 
         // No bug reports were requested so far.
-        assertEquals(-1, getLastSecurityLogRetrievalTime());
+        assertEquals(-1, dpm.getLastBugReportRequestTime());
 
         // Requesting a bug report should update the timestamp.
         final long beforeRequest = System.currentTimeMillis();
         dpm.requestBugreport(admin1);
-        final long bugReportRequestTime = getLastBugReportRequestTime();
+        final long bugReportRequestTime = dpm.getLastBugReportRequestTime();
         final long afterRequest = System.currentTimeMillis();
         assertTrue(bugReportRequestTime >= beforeRequest);
         assertTrue(bugReportRequestTime <= afterRequest);
 
         // Checking the timestamp again should not change it.
         Thread.sleep(2);
-        assertEquals(bugReportRequestTime, getLastBugReportRequestTime());
+        assertEquals(bugReportRequestTime, dpm.getLastBugReportRequestTime());
 
         // Restarting the DPMS should not lose the timestamp.
         initializeDpms();
-        assertEquals(bugReportRequestTime, getLastBugReportRequestTime());
-    }
+        assertEquals(bugReportRequestTime, dpm.getLastBugReportRequestTime());
 
-    private long getLastNetworkLogRetrievalTime() {
-        final long ident = mContext.binder.clearCallingIdentity();
-        final long lastNetworkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
-        mContext.binder.restoreCallingIdentity(ident);
-        return lastNetworkLogRetrievalTime;
+        // Any uid holding MANAGE_USERS permission can retrieve the timestamp.
+        mContext.binder.callingUid = 1234567;
+        mContext.callerPermissions.add(permission.MANAGE_USERS);
+        assertEquals(bugReportRequestTime, dpm.getLastBugReportRequestTime());
+        mContext.callerPermissions.remove(permission.MANAGE_USERS);
+
+        // System can retrieve the timestamp.
+        mContext.binder.clearCallingIdentity();
+        assertEquals(bugReportRequestTime, dpm.getLastBugReportRequestTime());
+
+        // Removing the device owner should clear the timestamp.
+        clearDeviceOwner();
+        assertEquals(-1, dpm.getLastBugReportRequestTime());
     }
 
     public void testGetLastNetworkLogRetrievalTime() throws Exception {
@@ -2380,41 +2396,55 @@
                 .thenReturn(true);
 
         // No logs were retrieved so far.
-        assertEquals(-1, getLastNetworkLogRetrievalTime());
+        assertEquals(-1, dpm.getLastNetworkLogRetrievalTime());
 
         // Attempting to retrieve logs without enabling logging should not change the timestamp.
         dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
-        assertEquals(-1, getLastNetworkLogRetrievalTime());
+        assertEquals(-1, dpm.getLastNetworkLogRetrievalTime());
 
         // Enabling logging should not change the timestamp.
         dpm.setNetworkLoggingEnabled(admin1, true);
-        assertEquals(-1, getLastNetworkLogRetrievalTime());
+        assertEquals(-1, dpm.getLastNetworkLogRetrievalTime());
 
         // Retrieving the logs should update the timestamp.
         final long beforeRetrieval = System.currentTimeMillis();
         dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
-        final long firstNetworkLogRetrievalTime = getLastNetworkLogRetrievalTime();
+        final long firstNetworkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
         final long afterRetrieval = System.currentTimeMillis();
         assertTrue(firstNetworkLogRetrievalTime >= beforeRetrieval);
         assertTrue(firstNetworkLogRetrievalTime <= afterRetrieval);
 
         // Checking the timestamp again should not change it.
         Thread.sleep(2);
-        assertEquals(firstNetworkLogRetrievalTime, getLastNetworkLogRetrievalTime());
+        assertEquals(firstNetworkLogRetrievalTime, dpm.getLastNetworkLogRetrievalTime());
 
         // Retrieving the logs again should update the timestamp.
         dpm.retrieveNetworkLogs(admin1, 0 /* batchToken */);
-        final long secondNetworkLogRetrievalTime = getLastNetworkLogRetrievalTime();
+        final long secondNetworkLogRetrievalTime = dpm.getLastNetworkLogRetrievalTime();
         assertTrue(secondNetworkLogRetrievalTime > firstNetworkLogRetrievalTime);
 
         // Disabling logging should not change the timestamp.
         Thread.sleep(2);
         dpm.setNetworkLoggingEnabled(admin1, false);
-        assertEquals(secondNetworkLogRetrievalTime, getLastNetworkLogRetrievalTime());
+        assertEquals(secondNetworkLogRetrievalTime, dpm.getLastNetworkLogRetrievalTime());
 
         // Restarting the DPMS should not lose the timestamp.
         initializeDpms();
-        assertEquals(secondNetworkLogRetrievalTime, getLastNetworkLogRetrievalTime());
+        assertEquals(secondNetworkLogRetrievalTime, dpm.getLastNetworkLogRetrievalTime());
+
+        // Any uid holding MANAGE_USERS permission can retrieve the timestamp.
+        mContext.binder.callingUid = 1234567;
+        mContext.callerPermissions.add(permission.MANAGE_USERS);
+        assertEquals(secondNetworkLogRetrievalTime, dpm.getLastNetworkLogRetrievalTime());
+        mContext.callerPermissions.remove(permission.MANAGE_USERS);
+
+        // System can retrieve the timestamp.
+        mContext.binder.clearCallingIdentity();
+        assertEquals(secondNetworkLogRetrievalTime, dpm.getLastNetworkLogRetrievalTime());
+
+        // Removing the device owner should clear the timestamp.
+        clearDeviceOwner();
+        assertEquals(-1, dpm.getLastNetworkLogRetrievalTime());
     }
 
     private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java
new file mode 100644
index 0000000..d165b8b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/pm/ParallelPackageParserTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.pm;
+
+import android.content.pm.PackageParser;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Tests for {@link ParallelPackageParser}
+ */
+@RunWith(AndroidJUnit4.class)
+public class ParallelPackageParserTest {
+    private static final String TAG = ParallelPackageParserTest.class.getSimpleName();
+
+    private ParallelPackageParser mParser;
+
+    @Before
+    public void setUp() {
+        mParser = new TestParallelPackageParser();
+    }
+
+    @Test(timeout = 1000)
+    public void test() {
+        Set<File> submittedFiles = new HashSet<>();
+        int fileCount = 15;
+        for (int i = 0; i < fileCount; i++) {
+            File file = new File("f" + i);
+            mParser.submit(file, 0);
+            submittedFiles.add(file);
+            Log.d(TAG, "submitting " + file);
+        }
+        for (int i = 0; i < fileCount; i++) {
+            ParallelPackageParser.ParseResult result = mParser.take();
+            Assert.assertNotNull(result);
+            File parsedFile = result.scanFile;
+            Log.d(TAG, "took " + parsedFile);
+            Assert.assertNotNull(parsedFile);
+            boolean removeSuccessful = submittedFiles.remove(parsedFile);
+            Assert.assertTrue("Unexpected file " + parsedFile + ". Expected submitted files: "
+                    + submittedFiles, removeSuccessful);
+        }
+    }
+
+    class TestParallelPackageParser extends ParallelPackageParser {
+
+        TestParallelPackageParser() {
+            super(null, false, null);
+        }
+
+        @Override
+        protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,
+                int parseFlags) throws PackageParser.PackageParserException {
+            // Do not actually parse the package for testing
+            return null;
+        }
+    }
+}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 20dd012..457fd88 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -686,42 +686,49 @@
     public static final String EXTRA_DATA_FAILURE_CAUSE = PhoneConstants.DATA_FAILURE_CAUSE_KEY;
 
     /**
-     * Broadcast intent action for letting custom component know to show voicemail notification.
-     * @hide
+     * Broadcast intent action for letting the default dialer to know to show voicemail
+     * notification.
+     *
+     * <p>
+     * The {@link #EXTRA_NOTIFICATION_COUNT} extra indicates the total numbers of unheard
+     * voicemails.
+     * The {@link #EXTRA_VOICEMAIL_NUMBER} extra indicates the voicemail number if available.
+     * The {@link #EXTRA_CALL_VOICEMAIL_INTENT} extra is a {@link android.app.PendingIntent} that
+     * will call the voicemail number when sent. This extra will be empty if the voicemail number
+     * is not set, and {@link #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT} will be set instead.
+     * The {@link #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT} extra is a
+     * {@link android.app.PendingIntent} that will launch the voicemail settings. This extra is only
+     * available when the voicemail number is not set.
+     *
+     * @see #EXTRA_NOTIFICATION_COUNT
+     * @see #EXTRA_VOICEMAIL_NUMBER
+     * @see #EXTRA_CALL_VOICEMAIL_INTENT
+     * @see #EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT
      */
-    @SystemApi
     public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION =
             "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION";
 
     /**
      * The number of voice messages associated with the notification.
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_NOTIFICATION_COUNT =
             "android.telephony.extra.NOTIFICATION_COUNT";
 
     /**
      * The voicemail number.
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_VOICEMAIL_NUMBER =
             "android.telephony.extra.VOICEMAIL_NUMBER";
 
     /**
      * The intent to call voicemail.
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_CALL_VOICEMAIL_INTENT =
             "android.telephony.extra.CALL_VOICEMAIL_INTENT";
 
     /**
      * The intent to launch voicemail settings.
-     * @hide
      */
-    @SystemApi
     public static final String EXTRA_LAUNCH_VOICEMAIL_SETTINGS_INTENT =
             "android.telephony.extra.LAUNCH_VOICEMAIL_SETTINGS_INTENT";
 
@@ -4850,10 +4857,19 @@
         return false;
     }
 
-    /** @hide */
-    @SystemApi
+    /**
+     * Turns mobile data on or off.
+     *
+     * <p>Requires Permission:
+     *     {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the
+     *     calling app has carrier privileges.
+     *
+     * @param enable Whether to enable mobile data.
+     *
+     * @see #hasCarrierPrivileges
+     */
     public void setDataEnabled(boolean enable) {
-        setDataEnabled(SubscriptionManager.getDefaultDataSubscriptionId(), enable);
+        setDataEnabled(getSubId(), enable);
     }
 
     /** @hide */
@@ -4869,10 +4885,20 @@
         }
     }
 
-    /** @hide */
-    @SystemApi
+    /**
+     * Returns whether mobile data is enabled or not.
+     *
+     * <p>Requires Permission:
+     *     {@link android.Manifest.permission#ACCESS_NETWORK_STATE ACCESS_NETWORK_STATE},
+     *     {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}, or that the
+     *     calling app has carrier privileges.
+     *
+     * @return true if mobile data is enabled.
+     *
+     * @see #hasCarrierPrivileges
+     */
     public boolean getDataEnabled() {
-        return getDataEnabled(SubscriptionManager.getDefaultDataSubscriptionId());
+        return getDataEnabled(getSubId());
     }
 
     /** @hide */
diff --git a/test-runner/src/junit/MODULE_LICENSE_CPL b/test-runner/src/junit/MODULE_LICENSE_CPL
deleted file mode 100644
index 541dbb5..0000000
--- a/test-runner/src/junit/MODULE_LICENSE_CPL
+++ /dev/null
@@ -1 +0,0 @@
-http://www.opensource.org/licenses/cpl1.0.php
diff --git a/test-runner/src/junit/runner/ClassPathTestCollector.java b/test-runner/src/junit/runner/ClassPathTestCollector.java
deleted file mode 100644
index f48ddee..0000000
--- a/test-runner/src/junit/runner/ClassPathTestCollector.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package junit.runner;
-
-import java.util.*;
-import java.io.*;
-
-/**
- * An implementation of a TestCollector that consults the
- * class path. It considers all classes on the class path
- * excluding classes in JARs. It leaves it up to subclasses
- * to decide whether a class is a runnable Test.
- *
- * @see TestCollector
- * {@hide} - Not needed for 1.0 SDK
- */
-public abstract class ClassPathTestCollector implements TestCollector {
-
-    static final int SUFFIX_LENGTH= ".class".length();
-
-    public ClassPathTestCollector() {
-    }
-
-    public Enumeration collectTests() {
-        String classPath= System.getProperty("java.class.path");
-        Hashtable result = collectFilesInPath(classPath);
-        return result.elements();
-    }
-
-    public Hashtable collectFilesInPath(String classPath) {
-        Hashtable result= collectFilesInRoots(splitClassPath(classPath));
-        return result;
-    }
-
-    Hashtable collectFilesInRoots(Vector roots) {
-        Hashtable result= new Hashtable(100);
-        Enumeration e= roots.elements();
-        while (e.hasMoreElements())
-            gatherFiles(new File((String)e.nextElement()), "", result);
-        return result;
-    }
-
-    void gatherFiles(File classRoot, String classFileName, Hashtable result) {
-        File thisRoot= new File(classRoot, classFileName);
-        if (thisRoot.isFile()) {
-            if (isTestClass(classFileName)) {
-                String className= classNameFromFile(classFileName);
-                result.put(className, className);
-            }
-            return;
-        }
-        String[] contents= thisRoot.list();
-        if (contents != null) {
-            for (int i= 0; i < contents.length; i++)
-                gatherFiles(classRoot, classFileName+File.separatorChar+contents[i], result);
-        }
-    }
-
-    Vector splitClassPath(String classPath) {
-        Vector result= new Vector();
-        String separator= System.getProperty("path.separator");
-        StringTokenizer tokenizer= new StringTokenizer(classPath, separator);
-        while (tokenizer.hasMoreTokens())
-            result.addElement(tokenizer.nextToken());
-        return result;
-    }
-
-    protected boolean isTestClass(String classFileName) {
-        return
-                classFileName.endsWith(".class") &&
-                classFileName.indexOf('$') < 0 &&
-                classFileName.indexOf("Test") > 0;
-    }
-
-    protected String classNameFromFile(String classFileName) {
-        // convert /a/b.class to a.b
-        String s= classFileName.substring(0, classFileName.length()-SUFFIX_LENGTH);
-        String s2= s.replace(File.separatorChar, '.');
-        if (s2.startsWith("."))
-            return s2.substring(1);
-        return s2;
-    }
-}
diff --git a/test-runner/src/junit/runner/FailureDetailView.java b/test-runner/src/junit/runner/FailureDetailView.java
deleted file mode 100644
index c846191..0000000
--- a/test-runner/src/junit/runner/FailureDetailView.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package junit.runner;
-
-// The following line was removed for compatibility with Android libraries.
-//import java.awt.Component;
-
-import junit.framework.*;
-
-/**
- * A view to show a details about a failure
- * {@hide} - Not needed for 1.0 SDK
- */
-public interface FailureDetailView {
-    // The following definition was removed for compatibility with Android
-    // libraries.
-    //  /**
-    //   * Returns the component used to present the TraceView
-    //   */
-    //  public Component getComponent();
-
-    /**
-     * Shows details of a TestFailure
-     */
-    public void showFailure(TestFailure failure);
-    /**
-     * Clears the view
-     */
-    public void clear();
-}
diff --git a/test-runner/src/junit/runner/LoadingTestCollector.java b/test-runner/src/junit/runner/LoadingTestCollector.java
deleted file mode 100644
index 9101900..0000000
--- a/test-runner/src/junit/runner/LoadingTestCollector.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package junit.runner;
-
-import java.lang.reflect.*;
-import junit.framework.*;
-
-/**
- * An implementation of a TestCollector that loads
- * all classes on the class path and tests whether
- * it is assignable from Test or provides a static suite method.
- * @see TestCollector
- * {@hide} - Not needed for 1.0 SDK
- */
-public class LoadingTestCollector extends ClassPathTestCollector {
-
-    TestCaseClassLoader fLoader;
-
-    public LoadingTestCollector() {
-        fLoader= new TestCaseClassLoader();
-    }
-
-    protected boolean isTestClass(String classFileName) {
-        try {
-            if (classFileName.endsWith(".class")) {
-                Class testClass= classFromFile(classFileName);
-                return (testClass != null) && isTestClass(testClass);
-            }
-        }
-        catch (ClassNotFoundException expected) {
-        }
-        catch (NoClassDefFoundError notFatal) {
-        }
-        return false;
-    }
-
-    Class classFromFile(String classFileName) throws ClassNotFoundException {
-        String className= classNameFromFile(classFileName);
-        if (!fLoader.isExcluded(className))
-            return fLoader.loadClass(className, false);
-        return null;
-    }
-
-    boolean isTestClass(Class testClass) {
-        if (hasSuiteMethod(testClass))
-            return true;
-        if (Test.class.isAssignableFrom(testClass) &&
-                Modifier.isPublic(testClass.getModifiers()) &&
-                hasPublicConstructor(testClass))
-            return true;
-        return false;
-    }
-
-    boolean hasSuiteMethod(Class testClass) {
-        try {
-            testClass.getMethod(BaseTestRunner.SUITE_METHODNAME, new Class[0]);
-        } catch(Exception e) {
-            return false;
-        }
-        return true;
-    }
-
-    boolean hasPublicConstructor(Class testClass) {
-        try {
-            TestSuite.getTestConstructor(testClass);
-        } catch(NoSuchMethodException e) {
-            return false;
-        }
-        return true;
-    }
-}
diff --git a/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java b/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
deleted file mode 100644
index c4d80d0..0000000
--- a/test-runner/src/junit/runner/ReloadingTestSuiteLoader.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package junit.runner;
-
-/**
- * A TestSuite loader that can reload classes.
- * {@hide} - Not needed for 1.0 SDK
- */
-public class ReloadingTestSuiteLoader implements TestSuiteLoader {
-
-    public Class load(String suiteClassName) throws ClassNotFoundException {
-        return createLoader().loadClass(suiteClassName, true);
-    }
-
-    public Class reload(Class aClass) throws ClassNotFoundException {
-        return createLoader().loadClass(aClass.getName(), true);
-    }
-
-    protected TestCaseClassLoader createLoader() {
-        return new TestCaseClassLoader();
-    }
-}
diff --git a/test-runner/src/junit/runner/SimpleTestCollector.java b/test-runner/src/junit/runner/SimpleTestCollector.java
deleted file mode 100644
index 6cb0e19..0000000
--- a/test-runner/src/junit/runner/SimpleTestCollector.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package junit.runner;
-
-/**
- * An implementation of a TestCollector that considers
- * a class to be a test class when it contains the
- * pattern "Test" in its name
- * @see TestCollector
- * {@hide} - Not needed for 1.0 SDK
- */
-public class SimpleTestCollector extends ClassPathTestCollector {
-
-    public SimpleTestCollector() {
-    }
-
-    protected boolean isTestClass(String classFileName) {
-        return
-                classFileName.endsWith(".class") &&
-                classFileName.indexOf('$') < 0 &&
-                classFileName.indexOf("Test") > 0;
-    }
-}
diff --git a/test-runner/src/junit/runner/Sorter.java b/test-runner/src/junit/runner/Sorter.java
deleted file mode 100644
index 8d9341d..0000000
--- a/test-runner/src/junit/runner/Sorter.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package junit.runner;
-
-import java.util.*;
-
-/**
- * A custom quick sort with support to customize the swap behaviour.
- * NOTICE: We can't use the the sorting support from the JDK 1.2 collection
- * classes because of the JDK 1.1.7 compatibility.
- * {@hide} - Not needed for 1.0 SDK
- */
-public class Sorter {
-    public static interface Swapper {
-        public void swap(Vector values, int left, int right);
-    }
-
-    public static void sortStrings(Vector values , int left, int right, Swapper swapper) {
-        int oleft= left;
-        int oright= right;
-        String mid= (String)values.elementAt((left + right) / 2);
-        do {
-            while (((String)(values.elementAt(left))).compareTo(mid) < 0)
-                left++;
-            while (mid.compareTo((String)(values.elementAt(right))) < 0)
-                right--;
-            if (left <= right) {
-                swapper.swap(values, left, right);
-                left++;
-                right--;
-            }
-        } while (left <= right);
-
-        if (oleft < right)
-            sortStrings(values, oleft, right, swapper);
-        if (left < oright)
-            sortStrings(values, left, oright, swapper);
-    }
-}
diff --git a/test-runner/src/junit/runner/TestCaseClassLoader.java b/test-runner/src/junit/runner/TestCaseClassLoader.java
deleted file mode 100644
index 09eec7f..0000000
--- a/test-runner/src/junit/runner/TestCaseClassLoader.java
+++ /dev/null
@@ -1,225 +0,0 @@
-package junit.runner;
-
-import java.util.*;
-import java.io.*;
-import java.net.URL;
-import java.util.zip.*;
-
-/**
- * A custom class loader which enables the reloading
- * of classes for each test run. The class loader
- * can be configured with a list of package paths that
- * should be excluded from loading. The loading
- * of these packages is delegated to the system class
- * loader. They will be shared across test runs.
- * <p>
- * The list of excluded package paths is specified in
- * a properties file "excluded.properties" that is located in
- * the same place as the TestCaseClassLoader class.
- * <p>
- * <b>Known limitation:</b> the TestCaseClassLoader cannot load classes
- * from jar files.
- * {@hide} - Not needed for 1.0 SDK
- */
-public class TestCaseClassLoader extends ClassLoader {
-    /** scanned class path */
-    private Vector fPathItems;
-    /** default excluded paths */
-    private String[] defaultExclusions= {
-            "junit.framework.",
-            "junit.extensions.",
-            "junit.runner."
-    };
-    /** name of excluded properties file */
-    static final String EXCLUDED_FILE= "excluded.properties";
-    /** excluded paths */
-    private Vector fExcluded;
-
-    /**
-     * Constructs a TestCaseLoader. It scans the class path
-     * and the excluded package paths
-     */
-    public TestCaseClassLoader() {
-        this(System.getProperty("java.class.path"));
-    }
-
-    /**
-     * Constructs a TestCaseLoader. It scans the class path
-     * and the excluded package paths
-     */
-    public TestCaseClassLoader(String classPath) {
-        scanPath(classPath);
-        readExcludedPackages();
-    }
-
-    private void scanPath(String classPath) {
-        String separator= System.getProperty("path.separator");
-        fPathItems= new Vector(10);
-        StringTokenizer st= new StringTokenizer(classPath, separator);
-        while (st.hasMoreTokens()) {
-            fPathItems.addElement(st.nextToken());
-        }
-    }
-
-    public URL getResource(String name) {
-        return ClassLoader.getSystemResource(name);
-    }
-
-    public InputStream getResourceAsStream(String name) {
-        return ClassLoader.getSystemResourceAsStream(name);
-    }
-
-    public boolean isExcluded(String name) {
-        for (int i= 0; i < fExcluded.size(); i++) {
-            if (name.startsWith((String) fExcluded.elementAt(i))) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public synchronized Class loadClass(String name, boolean resolve)
-            throws ClassNotFoundException {
-
-        Class c= findLoadedClass(name);
-        if (c != null)
-            return c;
-        //
-        // Delegate the loading of excluded classes to the
-        // standard class loader.
-        //
-        if (isExcluded(name)) {
-            try {
-                c= findSystemClass(name);
-                return c;
-            } catch (ClassNotFoundException e) {
-                // keep searching
-            }
-        }
-        if (c == null) {
-            byte[] data= lookupClassData(name);
-            if (data == null)
-                throw new ClassNotFoundException();
-            c= defineClass(name, data, 0, data.length);
-        }
-        if (resolve)
-            resolveClass(c);
-        return c;
-    }
-
-    private byte[] lookupClassData(String className) throws ClassNotFoundException {
-        byte[] data= null;
-        for (int i= 0; i < fPathItems.size(); i++) {
-            String path= (String) fPathItems.elementAt(i);
-            String fileName= className.replace('.', '/')+".class";
-            if (isJar(path)) {
-                data= loadJarData(path, fileName);
-            } else {
-                data= loadFileData(path, fileName);
-            }
-            if (data != null)
-                return data;
-        }
-        throw new ClassNotFoundException(className);
-    }
-
-    boolean isJar(String pathEntry) {
-        return pathEntry.endsWith(".jar") ||
-                pathEntry.endsWith(".apk") ||
-                pathEntry.endsWith(".zip");
-    }
-
-    private byte[] loadFileData(String path, String fileName) {
-        File file= new File(path, fileName);
-        if (file.exists()) {
-            return getClassData(file);
-        }
-        return null;
-    }
-
-    private byte[] getClassData(File f) {
-        try {
-            FileInputStream stream= new FileInputStream(f);
-            ByteArrayOutputStream out= new ByteArrayOutputStream(1000);
-            byte[] b= new byte[1000];
-            int n;
-            while ((n= stream.read(b)) != -1)
-                out.write(b, 0, n);
-            stream.close();
-            out.close();
-            return out.toByteArray();
-
-        } catch (IOException e) {
-        }
-        return null;
-    }
-
-    private byte[] loadJarData(String path, String fileName) {
-        ZipFile zipFile= null;
-        InputStream stream= null;
-        File archive= new File(path);
-        if (!archive.exists())
-            return null;
-        try {
-            zipFile= new ZipFile(archive);
-        } catch(IOException io) {
-            return null;
-        }
-        ZipEntry entry= zipFile.getEntry(fileName);
-        if (entry == null)
-            return null;
-        int size= (int) entry.getSize();
-        try {
-            stream= zipFile.getInputStream(entry);
-            byte[] data= new byte[size];
-            int pos= 0;
-            while (pos < size) {
-                int n= stream.read(data, pos, data.length - pos);
-                pos += n;
-            }
-            zipFile.close();
-            return data;
-        } catch (IOException e) {
-        } finally {
-            try {
-                if (stream != null)
-                    stream.close();
-            } catch (IOException e) {
-            }
-        }
-        return null;
-    }
-
-    private void readExcludedPackages() {
-        fExcluded= new Vector(10);
-        for (int i= 0; i < defaultExclusions.length; i++)
-            fExcluded.addElement(defaultExclusions[i]);
-
-        InputStream is= getClass().getResourceAsStream(EXCLUDED_FILE);
-        if (is == null)
-            return;
-        Properties p= new Properties();
-        try {
-            p.load(is);
-        }
-        catch (IOException e) {
-            return;
-        } finally {
-            try {
-                is.close();
-            } catch (IOException e) {
-            }
-        }
-        for (Enumeration e= p.propertyNames(); e.hasMoreElements(); ) {
-            String key= (String)e.nextElement();
-            if (key.startsWith("excluded.")) {
-                String path= p.getProperty(key);
-                path= path.trim();
-                if (path.endsWith("*"))
-                    path= path.substring(0, path.length()-1);
-                if (path.length() > 0)
-                    fExcluded.addElement(path);
-            }
-        }
-    }
-}
diff --git a/test-runner/src/junit/runner/TestCollector.java b/test-runner/src/junit/runner/TestCollector.java
deleted file mode 100644
index 3ac9d9e..0000000
--- a/test-runner/src/junit/runner/TestCollector.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package junit.runner;
-
-import java.util.*;
-
-
-/**
- * Collects Test class names to be presented
- * by the TestSelector.
- * @see TestSelector
- * {@hide} - Not needed for 1.0 SDK
- */
-public interface TestCollector {
-    /**
-     * Returns an enumeration of Strings with qualified class names
-     */
-    public Enumeration collectTests();
-}
diff --git a/test-runner/src/junit/runner/excluded.properties b/test-runner/src/junit/runner/excluded.properties
deleted file mode 100644
index 3284628..0000000
--- a/test-runner/src/junit/runner/excluded.properties
+++ /dev/null
@@ -1,12 +0,0 @@
-#
-# The list of excluded package paths for the TestCaseClassLoader
-#
-excluded.0=sun.*
-excluded.1=com.sun.*
-excluded.2=org.omg.*
-excluded.3=javax.*
-excluded.4=sunw.*
-excluded.5=java.*
-excluded.6=org.w3c.dom.*
-excluded.7=org.xml.sax.*
-excluded.8=net.jini.*
diff --git a/test-runner/src/junit/runner/package.html b/test-runner/src/junit/runner/package.html
deleted file mode 100644
index f08fa70..0000000
--- a/test-runner/src/junit/runner/package.html
+++ /dev/null
@@ -1,5 +0,0 @@
-<HTML>
-<BODY>
-Utility classes supporting the junit test framework.
-</BODY>
-</HTML>
diff --git a/test-runner/src/junit/textui/package.html b/test-runner/src/junit/textui/package.html
deleted file mode 100644
index 723f2ae..0000000
--- a/test-runner/src/junit/textui/package.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<HTML>
-<BODY>
-Utility classes supporting the junit test framework.
-{@hide} - Not needed for 1.0 SDK
-</BODY>
-</HTML>
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 4a6fb13..d62c30d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -90,6 +90,7 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.BooleanSupplier;
 
 /**
  * Tests for {@link ConnectivityService}.
@@ -623,7 +624,7 @@
     }
 
     private class WrappedAvoidBadWifiTracker extends AvoidBadWifiTracker {
-        public boolean configRestrictsAvoidBadWifi;
+        public volatile boolean configRestrictsAvoidBadWifi;
 
         public WrappedAvoidBadWifiTracker(Context c, Handler h, Runnable r) {
             super(c, h, r);
@@ -728,10 +729,7 @@
     static private void waitFor(Criteria criteria) {
         int delays = 0;
         while (!criteria.get()) {
-            try {
-                Thread.sleep(50);
-            } catch (InterruptedException e) {
-            }
+            sleepFor(50);
             if (++delays == 10) fail();
         }
     }
@@ -2001,6 +1999,7 @@
 
     @SmallTest
     public void testRequestBenchmark() throws Exception {
+        // TODO: turn this unit test into a real benchmarking test.
         // Benchmarks connecting and switching performance in the presence of a large number of
         // NetworkRequests.
         // 1. File NUM_REQUESTS requests.
@@ -2014,61 +2013,80 @@
         final CountDownLatch availableLatch = new CountDownLatch(NUM_REQUESTS);
         final CountDownLatch losingLatch = new CountDownLatch(NUM_REQUESTS);
 
-        final int REGISTER_TIME_LIMIT_MS = 100;
-        long startTime = System.currentTimeMillis();
         for (int i = 0; i < NUM_REQUESTS; i++) {
             callbacks[i] = new NetworkCallback() {
                 @Override public void onAvailable(Network n) { availableLatch.countDown(); }
                 @Override public void onLosing(Network n, int t) { losingLatch.countDown(); }
             };
-            mCm.registerNetworkCallback(request, callbacks[i]);
         }
-        long timeTaken = System.currentTimeMillis() - startTime;
-        String msg = String.format("Register %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, REGISTER_TIME_LIMIT_MS);
-        Log.d(TAG, msg);
-        assertTrue(msg, timeTaken < REGISTER_TIME_LIMIT_MS);
 
-        final int CONNECT_TIME_LIMIT_MS = 30;
+        final int REGISTER_TIME_LIMIT_MS = 180;
+        assertTimeLimit("Registering callbacks", REGISTER_TIME_LIMIT_MS, () -> {
+            for (NetworkCallback cb : callbacks) {
+                mCm.registerNetworkCallback(request, cb);
+            }
+        });
+
+        final int CONNECT_TIME_LIMIT_MS = 40;
         mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR);
         // Don't request that the network validate, because otherwise connect() will block until
         // the network gets NET_CAPABILITY_VALIDATED, after all the callbacks below have fired,
         // and we won't actually measure anything.
         mCellNetworkAgent.connect(false);
-        startTime = System.currentTimeMillis();
-        if (!availableLatch.await(CONNECT_TIME_LIMIT_MS, TimeUnit.MILLISECONDS)) {
-            fail(String.format("Only dispatched %d/%d onAvailable callbacks in %dms",
-                    NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
-                    CONNECT_TIME_LIMIT_MS));
-        }
-        timeTaken = System.currentTimeMillis() - startTime;
-        Log.d(TAG, String.format("Connect, %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, CONNECT_TIME_LIMIT_MS));
 
-        final int SWITCH_TIME_LIMIT_MS = 30;
+        long onAvailableDispatchingDuration = durationOf(() -> {
+            if (!awaitLatch(availableLatch, CONNECT_TIME_LIMIT_MS)) {
+                fail(String.format("Only dispatched %d/%d onAvailable callbacks in %dms",
+                        NUM_REQUESTS - availableLatch.getCount(), NUM_REQUESTS,
+                        CONNECT_TIME_LIMIT_MS));
+            }
+        });
+        Log.d(TAG, String.format("Connect, %d callbacks: %dms, acceptable %dms",
+                NUM_REQUESTS, onAvailableDispatchingDuration, CONNECT_TIME_LIMIT_MS));
+
+        final int SWITCH_TIME_LIMIT_MS = 40;
         mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
         // Give wifi a high enough score that we'll linger cell when wifi comes up.
         mWiFiNetworkAgent.adjustScore(40);
         mWiFiNetworkAgent.connect(false);
-        startTime = System.currentTimeMillis();
-        if (!losingLatch.await(SWITCH_TIME_LIMIT_MS, TimeUnit.MILLISECONDS)) {
-            fail(String.format("Only dispatched %d/%d onLosing callbacks in %dms",
-                    NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, SWITCH_TIME_LIMIT_MS));
-        }
-        timeTaken = System.currentTimeMillis() - startTime;
+
+        long onLostDispatchingDuration = durationOf(() -> {
+            if (!awaitLatch(losingLatch, SWITCH_TIME_LIMIT_MS)) {
+                fail(String.format("Only dispatched %d/%d onLosing callbacks in %dms",
+                        NUM_REQUESTS - losingLatch.getCount(), NUM_REQUESTS, SWITCH_TIME_LIMIT_MS));
+            }
+        });
         Log.d(TAG, String.format("Linger, %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, SWITCH_TIME_LIMIT_MS));
+                NUM_REQUESTS, onLostDispatchingDuration, SWITCH_TIME_LIMIT_MS));
 
         final int UNREGISTER_TIME_LIMIT_MS = 10;
-        startTime = System.currentTimeMillis();
-        for (int i = 0; i < NUM_REQUESTS; i++) {
-            mCm.unregisterNetworkCallback(callbacks[i]);
-        }
-        timeTaken = System.currentTimeMillis() - startTime;
-        msg = String.format("Unregister %d callbacks: %dms, acceptable %dms",
-                NUM_REQUESTS, timeTaken, UNREGISTER_TIME_LIMIT_MS);
+        assertTimeLimit("Unregistering callbacks", UNREGISTER_TIME_LIMIT_MS, () -> {
+            for (NetworkCallback cb : callbacks) {
+                mCm.unregisterNetworkCallback(cb);
+            }
+        });
+    }
+
+    private long durationOf(Runnable fn) {
+        long startTime = SystemClock.elapsedRealtime();
+        fn.run();
+        return SystemClock.elapsedRealtime() - startTime;
+    }
+
+    private void assertTimeLimit(String descr, long timeLimit, Runnable fn) {
+        long timeTaken = durationOf(fn);
+        String msg = String.format("%s: took %dms, limit was %dms", descr, timeTaken, timeLimit);
         Log.d(TAG, msg);
-        assertTrue(msg, timeTaken < UNREGISTER_TIME_LIMIT_MS);
+        assertTrue(msg, timeTaken <= timeLimit);
+    }
+
+    private boolean awaitLatch(CountDownLatch l, long timeoutMs) {
+        try {
+            if (l.await(timeoutMs, TimeUnit.MILLISECONDS)) {
+                return true;
+            }
+        } catch (InterruptedException e) {}
+        return false;
     }
 
     @SmallTest
@@ -2149,7 +2167,7 @@
             tracker.reevaluate();
             mService.waitForIdle();
             String msg = String.format("config=false, setting=%s", values[i]);
-            assertTrue(msg, mService.avoidBadWifi());
+            assertEventuallyTrue(() -> mService.avoidBadWifi(), 50);
             assertFalse(msg, tracker.shouldNotifyWifiUnvalidated());
         }
 
@@ -2158,19 +2176,19 @@
         Settings.Global.putInt(cr, settingName, 0);
         tracker.reevaluate();
         mService.waitForIdle();
-        assertFalse(mService.avoidBadWifi());
+        assertEventuallyTrue(() -> !mService.avoidBadWifi(), 50);
         assertFalse(tracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putInt(cr, settingName, 1);
         tracker.reevaluate();
         mService.waitForIdle();
-        assertTrue(mService.avoidBadWifi());
+        assertEventuallyTrue(() -> mService.avoidBadWifi(), 50);
         assertFalse(tracker.shouldNotifyWifiUnvalidated());
 
         Settings.Global.putString(cr, settingName, null);
         tracker.reevaluate();
         mService.waitForIdle();
-        assertFalse(mService.avoidBadWifi());
+        assertEventuallyTrue(() -> !mService.avoidBadWifi(), 50);
         assertTrue(tracker.shouldNotifyWifiUnvalidated());
     }
 
@@ -2313,10 +2331,30 @@
         networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
 
         // pass timeout and validate that UNAVAILABLE is not called
-        try {
-            Thread.sleep(15);
-        } catch (InterruptedException e) {
-        }
+        sleepFor(15);
+        networkCallback.assertNoCallback();
+    }
+
+    /**
+     * Validate that a satisfied network request followed by a disconnected (lost) network does
+     * not trigger onUnavailable() once the time-out period expires.
+     */
+    @SmallTest
+    public void testSatisfiedThenLostNetworkRequestDoesNotTriggerOnUnavailable() {
+        NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
+                NetworkCapabilities.TRANSPORT_WIFI).build();
+        final TestNetworkCallback networkCallback = new TestNetworkCallback();
+        mCm.requestNetwork(nr, networkCallback, 500);
+
+        mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI);
+        mWiFiNetworkAgent.connect(false);
+        networkCallback.expectCallback(CallbackState.AVAILABLE, mWiFiNetworkAgent);
+        sleepFor(20);
+        mWiFiNetworkAgent.disconnect();
+        networkCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
+
+        // pass timeout and validate that UNAVAILABLE is not called
+        sleepFor(600);
         networkCallback.assertNoCallback();
     }
 
@@ -2358,10 +2396,7 @@
         // pass timeout and validate that no callbacks
         // Note: doesn't validate that nothing called from CS since even if called the CM already
         // unregisters the callback and won't pass it through!
-        try {
-            Thread.sleep(15);
-        } catch (InterruptedException e) {
-        }
+        sleepFor(15);
         networkCallback.assertNoCallback();
 
         // create a network satisfying request - validate that request not triggered
@@ -2370,6 +2405,17 @@
         networkCallback.assertNoCallback();
     }
 
+    public void assertEventuallyTrue(BooleanSupplier fn, long maxWaitingTimeMs) throws Exception {
+        long start = SystemClock.elapsedRealtime();
+        while (SystemClock.elapsedRealtime() <= start + maxWaitingTimeMs) {
+            if (fn.getAsBoolean()) {
+                return;
+            }
+            Thread.sleep(10);
+        }
+        assertTrue(fn.getAsBoolean());
+    }
+
     private static class TestKeepaliveCallback extends PacketKeepaliveCallback {
 
         public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
@@ -2775,4 +2821,13 @@
             mCm.unregisterNetworkCallback(pendingIntent);
         }
     }
+
+    /* test utilities */
+    static private void sleepFor(int ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+        }
+
+    }
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java
new file mode 100644
index 0000000..df85806
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/BaseCanvas_Delegate.java
@@ -0,0 +1,727 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.layoutlib.bridge.impl.GcSnapshot;
+import com.android.layoutlib.bridge.impl.PorterDuffUtility;
+import com.android.ninepatch.NinePatchChunk;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import android.text.TextUtils;
+
+import java.awt.*;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Arc2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+
+public class BaseCanvas_Delegate {
+    // ---- delegate manager ----
+    protected static DelegateManager<BaseCanvas_Delegate> sManager =
+            new DelegateManager<>(BaseCanvas_Delegate.class);
+
+    // ---- delegate helper data ----
+    private final static boolean[] sBoolOut = new boolean[1];
+
+
+    // ---- delegate data ----
+    protected Bitmap_Delegate mBitmap;
+    protected GcSnapshot mSnapshot;
+
+    // ---- Public Helper methods ----
+
+    protected BaseCanvas_Delegate(Bitmap_Delegate bitmap) {
+        mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap);
+    }
+
+    protected BaseCanvas_Delegate() {
+        mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
+    }
+
+    /**
+     * Disposes of the {@link Graphics2D} stack.
+     */
+    protected void dispose() {
+        mSnapshot.dispose();
+    }
+
+    /**
+     * Returns the current {@link Graphics2D} used to draw.
+     */
+    public GcSnapshot getSnapshot() {
+        return mSnapshot;
+    }
+
+    // ---- native methods ----
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float left, float top,
+            long nativePaintOrZero, int canvasDensity, int screenDensity, int bitmapDensity) {
+        // get the delegate from the native int.
+        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+        if (bitmapDelegate == null) {
+            return;
+        }
+
+        BufferedImage image = bitmapDelegate.getImage();
+        float right = left + image.getWidth();
+        float bottom = top + image.getHeight();
+
+        drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
+                0, 0, image.getWidth(), image.getHeight(),
+                (int)left, (int)top, (int)right, (int)bottom);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawBitmap(long nativeCanvas, Bitmap bitmap, float srcLeft, float srcTop,
+            float srcRight, float srcBottom, float dstLeft, float dstTop, float dstRight,
+            float dstBottom, long nativePaintOrZero, int screenDensity, int bitmapDensity) {
+        // get the delegate from the native int.
+        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+        if (bitmapDelegate == null) {
+            return;
+        }
+
+        drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, (int) srcLeft, (int) srcTop,
+                (int) srcRight, (int) srcBottom, (int) dstLeft, (int) dstTop, (int) dstRight,
+                (int) dstBottom);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawBitmap(long nativeCanvas, int[] colors, int offset, int stride,
+            final float x, final float y, int width, int height, boolean hasAlpha,
+            long nativePaintOrZero) {
+        // create a temp BufferedImage containing the content.
+        final BufferedImage image = new BufferedImage(width, height,
+                hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
+        image.setRGB(0, 0, width, height, colors, offset, stride);
+
+        draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paint) -> {
+                    if (paint != null && paint.isFilterBitmap()) {
+                        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                    }
+
+                    graphics.drawImage(image, (int) x, (int) y, null);
+                });
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawColor(long nativeCanvas, final int color, final int mode) {
+        // get the delegate from the native int.
+        BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        final int w = canvasDelegate.mBitmap.getImage().getWidth();
+        final int h = canvasDelegate.mBitmap.getImage().getHeight();
+        draw(nativeCanvas, (graphics, paint) -> {
+            // reset its transform just in case
+            graphics.setTransform(new AffineTransform());
+
+            // set the color
+            graphics.setColor(new java.awt.Color(color, true /*alpha*/));
+
+            Composite composite = PorterDuffUtility.getComposite(
+                    PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
+            if (composite != null) {
+                graphics.setComposite(composite);
+            }
+
+            graphics.fillRect(0, 0, w, h);
+        });
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawPaint(long nativeCanvas, long paint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawPaint is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawPoint(long nativeCanvas, float x, float y,
+            long nativePaint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawPoint is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawPoints(long nativeCanvas, float[] pts, int offset, int count,
+            long nativePaint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawPoint is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawLine(long nativeCanvas,
+            final float startX, final float startY, final float stopX, final float stopY,
+            long paint) {
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paintDelegate) -> graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY));
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawLines(long nativeCanvas,
+            final float[] pts, final int offset, final int count,
+            long nativePaint) {
+        draw(nativeCanvas, nativePaint, false /*compositeOnly*/,
+                false /*forceSrcMode*/, (graphics, paintDelegate) -> {
+                    for (int i = 0; i < count; i += 4) {
+                        graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1],
+                                (int) pts[i + offset + 2], (int) pts[i + offset + 3]);
+                    }
+                });
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawRect(long nativeCanvas,
+            final float left, final float top, final float right, final float bottom, long paint) {
+
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paintDelegate) -> {
+                    int style = paintDelegate.getStyle();
+
+                    // draw
+                    if (style == Paint.Style.FILL.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.fillRect((int)left, (int)top,
+                                (int)(right-left), (int)(bottom-top));
+                    }
+
+                    if (style == Paint.Style.STROKE.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.drawRect((int)left, (int)top,
+                                (int)(right-left), (int)(bottom-top));
+                    }
+                });
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawOval(long nativeCanvas, final float left,
+            final float top, final float right, final float bottom, long paint) {
+        if (right > left && bottom > top) {
+            draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                    (graphics, paintDelegate) -> {
+                        int style = paintDelegate.getStyle();
+
+                        // draw
+                        if (style == Paint.Style.FILL.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.fillOval((int)left, (int)top,
+                                    (int)(right - left), (int)(bottom - top));
+                        }
+
+                        if (style == Paint.Style.STROKE.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.drawOval((int)left, (int)top,
+                                    (int)(right - left), (int)(bottom - top));
+                        }
+                    });
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawCircle(long nativeCanvas,
+            float cx, float cy, float radius, long paint) {
+        nDrawOval(nativeCanvas,
+                cx - radius, cy - radius, cx + radius, cy + radius,
+                paint);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawArc(long nativeCanvas,
+            final float left, final float top, final float right, final float bottom,
+            final float startAngle, final float sweep,
+            final boolean useCenter, long paint) {
+        if (right > left && bottom > top) {
+            draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                    (graphics, paintDelegate) -> {
+                        int style = paintDelegate.getStyle();
+
+                        Arc2D.Float arc = new Arc2D.Float(
+                                left, top, right - left, bottom - top,
+                                -startAngle, -sweep,
+                                useCenter ? Arc2D.PIE : Arc2D.OPEN);
+
+                        // draw
+                        if (style == Paint.Style.FILL.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.fill(arc);
+                        }
+
+                        if (style == Paint.Style.STROKE.nativeInt ||
+                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                            graphics.draw(arc);
+                        }
+                    });
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawRoundRect(long nativeCanvas,
+            final float left, final float top, final float right, final float bottom,
+            final float rx, final float ry, long paint) {
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paintDelegate) -> {
+                    int style = paintDelegate.getStyle();
+
+                    // draw
+                    if (style == Paint.Style.FILL.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.fillRoundRect(
+                                (int)left, (int)top,
+                                (int)(right - left), (int)(bottom - top),
+                                2 * (int)rx, 2 * (int)ry);
+                    }
+
+                    if (style == Paint.Style.STROKE.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.drawRoundRect(
+                                (int)left, (int)top,
+                                (int)(right - left), (int)(bottom - top),
+                                2 * (int)rx, 2 * (int)ry);
+                    }
+                });
+    }
+
+    @LayoutlibDelegate
+    public static void nDrawPath(long nativeCanvas, long path, long paint) {
+        final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
+        if (pathDelegate == null) {
+            return;
+        }
+
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paintDelegate) -> {
+                    Shape shape = pathDelegate.getJavaShape();
+                    Rectangle2D bounds = shape.getBounds2D();
+                    if (bounds.isEmpty()) {
+                        // Apple JRE 1.6 doesn't like drawing empty shapes.
+                        // http://b.android.com/178278
+
+                        if (pathDelegate.isEmpty()) {
+                            // This means that the path doesn't have any lines or curves so
+                            // nothing to draw.
+                            return;
+                        }
+
+                        // The stroke width is not consider for the size of the bounds so,
+                        // for example, a horizontal line, would be considered as an empty
+                        // rectangle.
+                        // If the strokeWidth is not 0, we use it to consider the size of the
+                        // path as well.
+                        float strokeWidth = paintDelegate.getStrokeWidth();
+                        if (strokeWidth <= 0.0f) {
+                            return;
+                        }
+                        bounds.setRect(bounds.getX(), bounds.getY(),
+                                Math.max(strokeWidth, bounds.getWidth()),
+                                Math.max(strokeWidth, bounds.getHeight()));
+                    }
+
+                    int style = paintDelegate.getStyle();
+
+                    if (style == Paint.Style.FILL.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.fill(shape);
+                    }
+
+                    if (style == Paint.Style.STROKE.nativeInt ||
+                            style == Paint.Style.FILL_AND_STROKE.nativeInt) {
+                        graphics.draw(shape);
+                    }
+                });
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawRegion(long nativeCanvas, long nativeRegion,
+            long nativePaint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Some canvas paths may not be drawn", null, null);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawNinePatch(long nativeCanvas, long nativeBitmap, long ninePatch,
+            final float dstLeft, final float dstTop, final float dstRight, final float dstBottom,
+            long nativePaintOrZero, final int screenDensity, final int bitmapDensity) {
+
+        // get the delegate from the native int.
+        final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap);
+        if (bitmapDelegate == null) {
+            return;
+        }
+
+        byte[] c = NinePatch_Delegate.getChunk(ninePatch);
+        if (c == null) {
+            // not a 9-patch?
+            BufferedImage image = bitmapDelegate.getImage();
+            drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(),
+                    image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight,
+                    (int) dstBottom);
+            return;
+        }
+
+        final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c);
+        assert chunkObject != null;
+        if (chunkObject == null) {
+            return;
+        }
+
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        // this one can be null
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
+
+        canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
+            @Override
+            public void draw(Graphics2D graphics, Paint_Delegate paint) {
+                chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop,
+                        (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity,
+                        bitmapDensity);
+            }
+        }, paintDelegate, true, false);
+
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
+            long nMatrix, long nPaint) {
+        // get the delegate from the native int.
+        BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        // get the delegate from the native int, which can be null
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
+
+        // get the delegate from the native int.
+        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
+        if (bitmapDelegate == null) {
+            return;
+        }
+
+        final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut);
+
+        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
+        if (matrixDelegate == null) {
+            return;
+        }
+
+        final AffineTransform mtx = matrixDelegate.getAffineTransform();
+
+        canvasDelegate.getSnapshot().draw((graphics, paint) -> {
+            if (paint != null && paint.isFilterBitmap()) {
+                graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                        RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+            }
+
+            //FIXME add support for canvas, screen and bitmap densities.
+            graphics.drawImage(image, mtx, null);
+        }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawBitmapMesh(long nCanvas, Bitmap bitmap,
+            int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
+            int colorOffset, long nPaint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawBitmapMesh is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawVertices(long nCanvas, int mode, int n,
+            float[] verts, int vertOffset,
+            float[] texs, int texOffset,
+            int[] colors, int colorOffset,
+            short[] indices, int indexOffset,
+            int indexCount, long nPaint) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawVertices is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawText(long nativeCanvas, char[] text, int index, int count,
+            float startX, float startY, int flags, long paint, long typeface) {
+        drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0,
+                paint, typeface);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawText(long nativeCanvas, String text,
+            int start, int end, float x, float y, final int flags, long paint,
+            long typeface) {
+        int count = end - start;
+        char[] buffer = TemporaryBuffer.obtain(count);
+        TextUtils.getChars(text, start, end, buffer, 0);
+
+        nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawTextRun(long nativeCanvas, String text,
+            int start, int end, int contextStart, int contextEnd,
+            float x, float y, boolean isRtl, long paint, long typeface) {
+        int count = end - start;
+        char[] buffer = TemporaryBuffer.obtain(count);
+        TextUtils.getChars(text, start, end, buffer, 0);
+
+        drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawTextRun(long nativeCanvas, char[] text,
+            int start, int count, int contextStart, int contextCount,
+            float x, float y, boolean isRtl, long paint, long typeface) {
+        drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawTextOnPath(long nativeCanvas,
+            char[] text, int index,
+            int count, long path,
+            float hOffset,
+            float vOffset, int bidiFlags,
+            long paint, long typeface) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nDrawTextOnPath(long nativeCanvas,
+            String text, long path,
+            float hOffset,
+            float vOffset,
+            int bidiFlags, long paint,
+            long typeface) {
+        // FIXME
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
+    }
+
+    // ---- Private delegate/helper methods ----
+
+    /**
+     * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint.
+     * <p>Note that the drawable may actually be executed several times if there are
+     * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
+     */
+    private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode,
+            GcSnapshot.Drawable drawable) {
+        // get the delegate from the native int.
+        BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        // get the paint which can be null if nPaint is 0;
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
+
+        canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode);
+    }
+
+    /**
+     * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided
+     * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
+     * <p>Note that the drawable may actually be executed several times if there are
+     * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
+     */
+    private static void draw(long nCanvas, GcSnapshot.Drawable drawable) {
+        // get the delegate from the native int.
+        BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        canvasDelegate.mSnapshot.draw(drawable);
+    }
+
+    private static void drawText(long nativeCanvas, final char[] text, final int index,
+            final int count, final float startX, final float startY, final boolean isRtl,
+            long paint, final long typeface) {
+
+        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
+                (graphics, paintDelegate) -> {
+                    // WARNING: the logic in this method is similar to Paint_Delegate.measureText.
+                    // Any change to this method should be reflected in Paint.measureText
+
+                    // assert that the typeface passed is actually the one stored in paint.
+                    assert (typeface == paintDelegate.mNativeTypeface);
+
+                    // Paint.TextAlign indicates how the text is positioned relative to X.
+                    // LEFT is the default and there's nothing to do.
+                    float x = startX;
+                    int limit = index + count;
+                    if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
+                        RectF bounds =
+                                paintDelegate.measureText(text, index, count, null, 0, isRtl);
+                        float m = bounds.right - bounds.left;
+                        if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
+                            x -= m / 2;
+                        } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
+                            x -= m;
+                        }
+                    }
+
+                    new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x,
+                            startY).renderText(index, limit, isRtl, null, 0, true);
+                });
+    }
+
+    private static void drawBitmap(long nativeCanvas, Bitmap_Delegate bitmap,
+            long nativePaintOrZero, final int sleft, final int stop, final int sright,
+            final int sbottom, final int dleft, final int dtop, final int dright,
+            final int dbottom) {
+        // get the delegate from the native int.
+        BaseCanvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        if (canvasDelegate == null) {
+            return;
+        }
+
+        // get the paint, which could be null if the int is 0
+        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
+
+        final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut);
+
+        draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0],
+                (graphics, paint) -> {
+                    if (paint != null && paint.isFilterBitmap()) {
+                        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
+                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
+                    }
+
+                    //FIXME add support for canvas, screen and bitmap densities.
+                    graphics.drawImage(image, dleft, dtop, dright, dbottom, sleft, stop, sright,
+                            sbottom, null);
+                });
+    }
+
+    /**
+     * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate.
+     * The image returns, through a 1-size boolean array, whether the drawing code should
+     * use a SRC composite no matter what the paint says.
+     *
+     * @param bitmap the bitmap
+     * @param paint the paint that will be used to draw
+     * @param forceSrcMode whether the composite will have to be SRC
+     * @return the image to draw
+     */
+    private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint,
+            boolean[] forceSrcMode) {
+        BufferedImage image = bitmap.getImage();
+        forceSrcMode[0] = false;
+
+        // if the bitmap config is alpha_8, then we erase all color value from it
+        // before drawing it.
+        if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
+            fixAlpha8Bitmap(image);
+        } else if (!bitmap.hasAlpha()) {
+            // hasAlpha is merely a rendering hint. There can in fact be alpha values
+            // in the bitmap but it should be ignored at drawing time.
+            // There is two ways to do this:
+            // - override the composite to be SRC. This can only be used if the composite
+            //   was going to be SRC or SRC_OVER in the first place
+            // - Create a different bitmap to draw in which all the alpha channel values is set
+            //   to 0xFF.
+            if (paint != null) {
+                PorterDuff.Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode());
+
+                forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER || mode == PorterDuff.Mode.SRC;
+            }
+
+            // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
+            if (!forceSrcMode[0]) {
+                image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
+            }
+        }
+
+        return image;
+    }
+
+    private static void fixAlpha8Bitmap(final BufferedImage image) {
+        int w = image.getWidth();
+        int h = image.getHeight();
+        int[] argb = new int[w * h];
+        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
+
+        final int length = argb.length;
+        for (int i = 0 ; i < length; i++) {
+            argb[i] &= 0xFF000000;
+        }
+        image.setRGB(0, 0, w, h, argb, 0, w);
+    }
+
+    protected int save(int saveFlags) {
+        // get the current save count
+        int count = mSnapshot.size();
+
+        mSnapshot = mSnapshot.save(saveFlags);
+
+        // return the old save count
+        return count;
+    }
+
+    protected int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
+        Paint_Delegate paint = new Paint_Delegate();
+        paint.setAlpha(alpha);
+        return saveLayer(rect, paint, saveFlags);
+    }
+
+    protected int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
+        // get the current save count
+        int count = mSnapshot.size();
+
+        mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
+
+        // return the old save count
+        return count;
+    }
+
+    /**
+     * Restores the {@link GcSnapshot} to <var>saveCount</var>
+     * @param saveCount the saveCount
+     */
+    protected void restoreTo(int saveCount) {
+        mSnapshot = mSnapshot.restoreTo(saveCount);
+    }
+
+    /**
+     * Restores the top {@link GcSnapshot}
+     */
+    protected void restore() {
+        mSnapshot = mSnapshot.restore();
+    }
+
+    protected boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
+        return mSnapshot.clipRect(left, top, right, bottom, regionOp);
+    }
+}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index 94152cd..43a0ff5 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -55,40 +55,27 @@
  * @see DelegateManager
  *
  */
-public final class Canvas_Delegate {
+public final class Canvas_Delegate extends BaseCanvas_Delegate {
 
     // ---- delegate manager ----
-    private static final DelegateManager<Canvas_Delegate> sManager =
-            new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class);
     private static long sFinalizer = -1;
 
-
-    // ---- delegate helper data ----
-
-    private final static boolean[] sBoolOut = new boolean[1];
-
-
-    // ---- delegate data ----
-    private Bitmap_Delegate mBitmap;
-    private GcSnapshot mSnapshot;
-
     private DrawFilter_Delegate mDrawFilter = null;
 
-
     // ---- Public Helper methods ----
 
     /**
      * Returns the native delegate associated to a given {@link Canvas} object.
      */
     public static Canvas_Delegate getDelegate(Canvas canvas) {
-        return sManager.getDelegate(canvas.getNativeCanvasWrapper());
+        return (Canvas_Delegate) sManager.getDelegate(canvas.getNativeCanvasWrapper());
     }
 
     /**
      * Returns the native delegate associated to a given an int referencing a {@link Canvas} object.
      */
     public static Canvas_Delegate getDelegate(long native_canvas) {
-        return sManager.getDelegate(native_canvas);
+        return (Canvas_Delegate) sManager.getDelegate(native_canvas);
     }
 
     /**
@@ -143,7 +130,7 @@
 
     @LayoutlibDelegate
     public static void nSetBitmap(long canvas, Bitmap bitmap) {
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
         Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
         if (canvasDelegate == null || bitmapDelegate==null) {
             return;
@@ -155,7 +142,7 @@
     @LayoutlibDelegate
     public static boolean nIsOpaque(long nativeCanvas) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return false;
         }
@@ -169,7 +156,7 @@
     @LayoutlibDelegate
     public static int nGetWidth(long nativeCanvas) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return 0;
         }
@@ -180,7 +167,7 @@
     @LayoutlibDelegate
     public static int nGetHeight(long nativeCanvas) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return 0;
         }
@@ -191,7 +178,7 @@
     @LayoutlibDelegate
     public static int nSave(long nativeCanvas, int saveFlags) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return 0;
         }
@@ -204,7 +191,7 @@
                                                float t, float r, float b,
                                                long paint, int layerFlags) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return 0;
         }
@@ -223,7 +210,7 @@
                                                     float t, float r, float b,
                                                     int alpha, int layerFlags) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return 0;
         }
@@ -235,7 +222,7 @@
     public static void nRestore(long nativeCanvas, boolean throwOnUnderflow) {
         // FIXME: implement throwOnUnderflow.
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return;
         }
@@ -248,7 +235,7 @@
             boolean throwOnUnderflow) {
         // FIXME: implement throwOnUnderflow.
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return;
         }
@@ -259,7 +246,7 @@
     @LayoutlibDelegate
     public static int nGetSaveCount(long nativeCanvas) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return 0;
         }
@@ -270,7 +257,7 @@
     @LayoutlibDelegate
    public static void nTranslate(long nativeCanvas, float dx, float dy) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return;
         }
@@ -281,7 +268,7 @@
     @LayoutlibDelegate
        public static void nScale(long nativeCanvas, float sx, float sy) {
             // get the delegate from the native int.
-            Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+            Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
             if (canvasDelegate == null) {
                 return;
             }
@@ -292,7 +279,7 @@
     @LayoutlibDelegate
     public static void nRotate(long nativeCanvas, float degrees) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return;
         }
@@ -303,7 +290,7 @@
     @LayoutlibDelegate
    public static void nSkew(long nativeCanvas, float kx, float ky) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return;
         }
@@ -327,7 +314,7 @@
     @LayoutlibDelegate
     public static void nConcat(long nCanvas, long nMatrix) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
         if (canvasDelegate == null) {
             return;
         }
@@ -355,7 +342,7 @@
     @LayoutlibDelegate
     public static void nSetMatrix(long nCanvas, long nMatrix) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
         if (canvasDelegate == null) {
             return;
         }
@@ -388,7 +375,7 @@
                                                   float right, float bottom,
                                                   int regionOp) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nCanvas);
         if (canvasDelegate == null) {
             return false;
         }
@@ -400,7 +387,7 @@
     public static boolean nClipPath(long nativeCanvas,
                                                   long nativePath,
                                                   int regionOp) {
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return true;
         }
@@ -417,7 +404,7 @@
     public static boolean nClipRegion(long nativeCanvas,
                                                     long nativeRegion,
                                                     int regionOp) {
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return true;
         }
@@ -432,7 +419,7 @@
 
     @LayoutlibDelegate
     public static void nSetDrawFilter(long nativeCanvas, long nativeFilter) {
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return;
         }
@@ -449,7 +436,7 @@
     public static boolean nGetClipBounds(long nativeCanvas,
                                                        Rect bounds) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
         if (canvasDelegate == null) {
             return false;
         }
@@ -469,7 +456,7 @@
     @LayoutlibDelegate
     public static void nGetCTM(long canvas, long matrix) {
         // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas);
+        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
         if (canvasDelegate == null) {
             return;
         }
@@ -498,509 +485,11 @@
     }
 
     @LayoutlibDelegate
-    public static void nDrawColor(long nativeCanvas, final int color, final int mode) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            return;
-        }
-
-        final int w = canvasDelegate.mBitmap.getImage().getWidth();
-        final int h = canvasDelegate.mBitmap.getImage().getHeight();
-        draw(nativeCanvas, new GcSnapshot.Drawable() {
-
-            @Override
-            public void draw(Graphics2D graphics, Paint_Delegate paint) {
-                // reset its transform just in case
-                graphics.setTransform(new AffineTransform());
-
-                // set the color
-                graphics.setColor(new Color(color, true /*alpha*/));
-
-                Composite composite = PorterDuffUtility.getComposite(
-                        PorterDuffUtility.getPorterDuffMode(mode), 0xFF);
-                if (composite != null) {
-                    graphics.setComposite(composite);
-                }
-
-                graphics.fillRect(0, 0, w, h);
-            }
-        });
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawPaint(long nativeCanvas, long paint) {
-        // FIXME
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                "Canvas.drawPaint is not supported.", null, null /*data*/);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawPoint(long nativeCanvas, float x, float y,
-            long nativePaint) {
-        // FIXME
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                "Canvas.drawPoint is not supported.", null, null /*data*/);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawPoints(long nativeCanvas, float[] pts, int offset, int count,
-            long nativePaint) {
-        // FIXME
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                "Canvas.drawPoint is not supported.", null, null /*data*/);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawLine(long nativeCanvas,
-            final float startX, final float startY, final float stopX, final float stopY,
-            long paint) {
-        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
-                new GcSnapshot.Drawable() {
-                    @Override
-                    public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
-                        graphics.drawLine((int)startX, (int)startY, (int)stopX, (int)stopY);
-                    }
-        });
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawLines(long nativeCanvas,
-            final float[] pts, final int offset, final int count,
-            long nativePaint) {
-        draw(nativeCanvas, nativePaint, false /*compositeOnly*/,
-                false /*forceSrcMode*/, new GcSnapshot.Drawable() {
-                    @Override
-                    public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
-                        for (int i = 0; i < count; i += 4) {
-                            graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1],
-                                    (int) pts[i + offset + 2], (int) pts[i + offset + 3]);
-                        }
-                    }
-                });
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawRect(long nativeCanvas,
-            final float left, final float top, final float right, final float bottom, long paint) {
-
-        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
-                new GcSnapshot.Drawable() {
-                    @Override
-                    public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
-                        int style = paintDelegate.getStyle();
-
-                        // draw
-                        if (style == Paint.Style.FILL.nativeInt ||
-                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                            graphics.fillRect((int)left, (int)top,
-                                    (int)(right-left), (int)(bottom-top));
-                        }
-
-                        if (style == Paint.Style.STROKE.nativeInt ||
-                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                            graphics.drawRect((int)left, (int)top,
-                                    (int)(right-left), (int)(bottom-top));
-                        }
-                    }
-        });
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawOval(long nativeCanvas, final float left,
-            final float top, final float right, final float bottom, long paint) {
-        if (right > left && bottom > top) {
-            draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
-                    new GcSnapshot.Drawable() {
-                        @Override
-                        public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
-                            int style = paintDelegate.getStyle();
-
-                            // draw
-                            if (style == Paint.Style.FILL.nativeInt ||
-                                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                                graphics.fillOval((int)left, (int)top,
-                                        (int)(right - left), (int)(bottom - top));
-                            }
-
-                            if (style == Paint.Style.STROKE.nativeInt ||
-                                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                                graphics.drawOval((int)left, (int)top,
-                                        (int)(right - left), (int)(bottom - top));
-                            }
-                        }
-            });
-        }
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawCircle(long nativeCanvas,
-            float cx, float cy, float radius, long paint) {
-        nDrawOval(nativeCanvas,
-                cx - radius, cy - radius, cx + radius, cy + radius,
-                paint);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawArc(long nativeCanvas,
-            final float left, final float top, final float right, final float bottom,
-            final float startAngle, final float sweep,
-            final boolean useCenter, long paint) {
-        if (right > left && bottom > top) {
-            draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
-                    new GcSnapshot.Drawable() {
-                        @Override
-                        public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
-                            int style = paintDelegate.getStyle();
-
-                            Arc2D.Float arc = new Arc2D.Float(
-                                    left, top, right - left, bottom - top,
-                                    -startAngle, -sweep,
-                                    useCenter ? Arc2D.PIE : Arc2D.OPEN);
-
-                            // draw
-                            if (style == Paint.Style.FILL.nativeInt ||
-                                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                                graphics.fill(arc);
-                            }
-
-                            if (style == Paint.Style.STROKE.nativeInt ||
-                                    style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                                graphics.draw(arc);
-                            }
-                        }
-            });
-        }
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawRoundRect(long nativeCanvas,
-            final float left, final float top, final float right, final float bottom,
-            final float rx, final float ry, long paint) {
-        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
-                new GcSnapshot.Drawable() {
-                    @Override
-                    public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
-                        int style = paintDelegate.getStyle();
-
-                        // draw
-                        if (style == Paint.Style.FILL.nativeInt ||
-                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                            graphics.fillRoundRect(
-                                    (int)left, (int)top,
-                                    (int)(right - left), (int)(bottom - top),
-                                    2 * (int)rx, 2 * (int)ry);
-                        }
-
-                        if (style == Paint.Style.STROKE.nativeInt ||
-                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                            graphics.drawRoundRect(
-                                    (int)left, (int)top,
-                                    (int)(right - left), (int)(bottom - top),
-                                    2 * (int)rx, 2 * (int)ry);
-                        }
-                    }
-        });
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawPath(long nativeCanvas, long path, long paint) {
-        final Path_Delegate pathDelegate = Path_Delegate.getDelegate(path);
-        if (pathDelegate == null) {
-            return;
-        }
-
-        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
-                new GcSnapshot.Drawable() {
-                    @Override
-                    public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
-                        Shape shape = pathDelegate.getJavaShape();
-                        Rectangle2D bounds = shape.getBounds2D();
-                        if (bounds.isEmpty()) {
-                            // Apple JRE 1.6 doesn't like drawing empty shapes.
-                            // http://b.android.com/178278
-
-                            if (pathDelegate.isEmpty()) {
-                                // This means that the path doesn't have any lines or curves so
-                                // nothing to draw.
-                                return;
-                            }
-
-                            // The stroke width is not consider for the size of the bounds so,
-                            // for example, a horizontal line, would be considered as an empty
-                            // rectangle.
-                            // If the strokeWidth is not 0, we use it to consider the size of the
-                            // path as well.
-                            float strokeWidth = paintDelegate.getStrokeWidth();
-                            if (strokeWidth <= 0.0f) {
-                                return;
-                            }
-                            bounds.setRect(bounds.getX(), bounds.getY(),
-                                    Math.max(strokeWidth, bounds.getWidth()),
-                                    Math.max(strokeWidth, bounds.getHeight()));
-                        }
-
-                        int style = paintDelegate.getStyle();
-
-                        if (style == Paint.Style.FILL.nativeInt ||
-                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                            graphics.fill(shape);
-                        }
-
-                        if (style == Paint.Style.STROKE.nativeInt ||
-                                style == Paint.Style.FILL_AND_STROKE.nativeInt) {
-                            graphics.draw(shape);
-                        }
-                    }
-        });
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawRegion(long nativeCanvas, long nativeRegion,
-            long nativePaint) {
-        // FIXME
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                "Some canvas paths may not be drawn", null, null);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawNinePatch(Canvas thisCanvas, long nativeCanvas,
-            long nativeBitmap, long ninePatch, final float dstLeft, final float dstTop,
-            final float dstRight, final float dstBottom, long nativePaintOrZero,
-            final int screenDensity, final int bitmapDensity) {
-
-        // get the delegate from the native int.
-        final Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmap);
-        if (bitmapDelegate == null) {
-            return;
-        }
-
-        byte[] c = NinePatch_Delegate.getChunk(ninePatch);
-        if (c == null) {
-            // not a 9-patch?
-            BufferedImage image = bitmapDelegate.getImage();
-            drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, 0, 0, image.getWidth(),
-                    image.getHeight(), (int) dstLeft, (int) dstTop, (int) dstRight,
-                    (int) dstBottom);
-            return;
-        }
-
-        final NinePatchChunk chunkObject = NinePatch_Delegate.getChunk(c);
-        assert chunkObject != null;
-        if (chunkObject == null) {
-            return;
-        }
-
-        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            return;
-        }
-
-        // this one can be null
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
-
-        canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
-            @Override
-            public void draw(Graphics2D graphics, Paint_Delegate paint) {
-                chunkObject.draw(bitmapDelegate.getImage(), graphics, (int) dstLeft, (int) dstTop,
-                        (int) (dstRight - dstLeft), (int) (dstBottom - dstTop), screenDensity,
-                        bitmapDensity);
-            }
-        }, paintDelegate, true, false);
-
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
-                                                 float left, float top,
-                                                 long nativePaintOrZero,
-                                                 int canvasDensity,
-                                                 int screenDensity,
-                                                 int bitmapDensity) {
-        // get the delegate from the native int.
-        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
-        if (bitmapDelegate == null) {
-            return;
-        }
-
-        BufferedImage image = bitmapDelegate.getImage();
-        float right = left + image.getWidth();
-        float bottom = top + image.getHeight();
-
-        drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
-                0, 0, image.getWidth(), image.getHeight(),
-                (int)left, (int)top, (int)right, (int)bottom);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawBitmap(Canvas thisCanvas, long nativeCanvas, Bitmap bitmap,
-                                 float srcLeft, float srcTop, float srcRight, float srcBottom,
-                                 float dstLeft, float dstTop, float dstRight, float dstBottom,
-                                 long nativePaintOrZero, int screenDensity, int bitmapDensity) {
-        // get the delegate from the native int.
-        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
-        if (bitmapDelegate == null) {
-            return;
-        }
-
-        drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero,
-                (int)srcLeft, (int)srcTop, (int)srcRight, (int)srcBottom,
-                (int)dstLeft, (int)dstTop, (int)dstRight, (int)dstBottom);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawBitmap(long nativeCanvas, int[] colors,
-                                                int offset, int stride, final float x,
-                                                 final float y, int width, int height,
-                                                 boolean hasAlpha,
-                                                 long nativePaintOrZero) {
-        // create a temp BufferedImage containing the content.
-        final BufferedImage image = new BufferedImage(width, height,
-                hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB);
-        image.setRGB(0, 0, width, height, colors, offset, stride);
-
-        draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, false /*forceSrcMode*/,
-                new GcSnapshot.Drawable() {
-                    @Override
-                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
-                        if (paint != null && paint.isFilterBitmap()) {
-                            graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
-                                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
-                        }
-
-                        graphics.drawImage(image, (int) x, (int) y, null);
-                    }
-        });
-    }
-
-    @LayoutlibDelegate
-    public static void nativeDrawBitmapMatrix(long nCanvas, Bitmap bitmap,
-                                                      long nMatrix, long nPaint) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
-        if (canvasDelegate == null) {
-            return;
-        }
-
-        // get the delegate from the native int, which can be null
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
-
-        // get the delegate from the native int.
-        Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap);
-        if (bitmapDelegate == null) {
-            return;
-        }
-
-        final BufferedImage image = getImageToDraw(bitmapDelegate, paintDelegate, sBoolOut);
-
-        Matrix_Delegate matrixDelegate = Matrix_Delegate.getDelegate(nMatrix);
-        if (matrixDelegate == null) {
-            return;
-        }
-
-        final AffineTransform mtx = matrixDelegate.getAffineTransform();
-
-        canvasDelegate.getSnapshot().draw(new GcSnapshot.Drawable() {
-                @Override
-                public void draw(Graphics2D graphics, Paint_Delegate paint) {
-                    if (paint != null && paint.isFilterBitmap()) {
-                        graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
-                                RenderingHints.VALUE_INTERPOLATION_BILINEAR);
-                    }
-
-                    //FIXME add support for canvas, screen and bitmap densities.
-                    graphics.drawImage(image, mtx, null);
-                }
-        }, paintDelegate, true /*compositeOnly*/, false /*forceSrcMode*/);
-    }
-
-    @LayoutlibDelegate
-    public static void nativeDrawBitmapMesh(long nCanvas, Bitmap bitmap,
-            int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,
-            int colorOffset, long nPaint) {
-        // FIXME
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                "Canvas.drawBitmapMesh is not supported.", null, null /*data*/);
-    }
-
-    @LayoutlibDelegate
-    public static void nativeDrawVertices(long nCanvas, int mode, int n,
-            float[] verts, int vertOffset,
-            float[] texs, int texOffset,
-            int[] colors, int colorOffset,
-            short[] indices, int indexOffset,
-            int indexCount, long nPaint) {
-        // FIXME
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                "Canvas.drawVertices is not supported.", null, null /*data*/);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawText(long nativeCanvas, char[] text, int index, int count,
-            float startX, float startY, int flags, long paint, long typeface) {
-        drawText(nativeCanvas, text, index, count, startX, startY, (flags & 1) != 0,
-                paint, typeface);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawText(long nativeCanvas, String text,
-            int start, int end, float x, float y, final int flags, long paint,
-            long typeface) {
-        int count = end - start;
-        char[] buffer = TemporaryBuffer.obtain(count);
-        TextUtils.getChars(text, start, end, buffer, 0);
-
-        nDrawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawTextRun(long nativeCanvas, String text,
-            int start, int end, int contextStart, int contextEnd,
-            float x, float y, boolean isRtl, long paint, long typeface) {
-        int count = end - start;
-        char[] buffer = TemporaryBuffer.obtain(count);
-        TextUtils.getChars(text, start, end, buffer, 0);
-
-        drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawTextRun(long nativeCanvas, char[] text,
-            int start, int count, int contextStart, int contextCount,
-            float x, float y, boolean isRtl, long paint, long typeface) {
-        drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawTextOnPath(long nativeCanvas,
-                                                     char[] text, int index,
-                                                     int count, long path,
-                                                     float hOffset,
-                                                     float vOffset, int bidiFlags,
-                                                     long paint, long typeface) {
-        // FIXME
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
-    }
-
-    @LayoutlibDelegate
-    public static void nDrawTextOnPath(long nativeCanvas,
-                                                     String text, long path,
-                                                     float hOffset,
-                                                     float vOffset,
-                                                     int bidiFlags, long paint,
-                                                     long typeface) {
-        // FIXME
-        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
-                "Canvas.drawTextOnPath is not supported.", null, null /*data*/);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static long getNativeFinalizer() {
+    /*package*/ static long nGetNativeFinalizer() {
         synchronized (Canvas_Delegate.class) {
             if (sFinalizer == -1) {
                 sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(nativePtr -> {
-                    Canvas_Delegate delegate = sManager.getDelegate(nativePtr);
+                    Canvas_Delegate delegate = Canvas_Delegate.getDelegate(nativePtr);
                     if (delegate != null) {
                         delegate.dispose();
                     }
@@ -1011,230 +500,12 @@
         return sFinalizer;
     }
 
-    // ---- Private delegate/helper methods ----
-
-    /**
-     * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint.
-     * <p>Note that the drawable may actually be executed several times if there are
-     * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
-     */
-    private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode,
-            GcSnapshot.Drawable drawable) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
-        if (canvasDelegate == null) {
-            return;
-        }
-
-        // get the paint which can be null if nPaint is 0;
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nPaint);
-
-        canvasDelegate.getSnapshot().draw(drawable, paintDelegate, compositeOnly, forceSrcMode);
-    }
-
-    /**
-     * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided
-     * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}.
-     * <p>Note that the drawable may actually be executed several times if there are
-     * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}.
-     */
-    private static void draw(long nCanvas, GcSnapshot.Drawable drawable) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas);
-        if (canvasDelegate == null) {
-            return;
-        }
-
-        canvasDelegate.mSnapshot.draw(drawable);
-    }
-
-    private static void drawText(long nativeCanvas, final char[] text, final int index,
-            final int count, final float startX, final float startY, final boolean isRtl,
-            long paint, final long typeface) {
-
-        draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/,
-                new GcSnapshot.Drawable() {
-            @Override
-            public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) {
-                // WARNING: the logic in this method is similar to Paint_Delegate.measureText.
-                // Any change to this method should be reflected in Paint.measureText
-
-                // assert that the typeface passed is actually the one stored in paint.
-                assert (typeface == paintDelegate.mNativeTypeface);
-
-                // Paint.TextAlign indicates how the text is positioned relative to X.
-                // LEFT is the default and there's nothing to do.
-                float x = startX;
-                int limit = index + count;
-                if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) {
-                    RectF bounds = paintDelegate.measureText(text, index, count, null, 0,
-                            isRtl);
-                    float m = bounds.right - bounds.left;
-                    if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) {
-                        x -= m / 2;
-                    } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) {
-                        x -= m;
-                    }
-                }
-
-                new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, startY)
-                        .renderText(index, limit, isRtl, null, 0, true);
-            }
-        });
-    }
-
     private Canvas_Delegate(Bitmap_Delegate bitmap) {
-        mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap);
+        super(bitmap);
     }
 
     private Canvas_Delegate() {
-        mSnapshot = GcSnapshot.createDefaultSnapshot(null /*image*/);
-    }
-
-    /**
-     * Disposes of the {@link Graphics2D} stack.
-     */
-    private void dispose() {
-        mSnapshot.dispose();
-    }
-
-    private int save(int saveFlags) {
-        // get the current save count
-        int count = mSnapshot.size();
-
-        mSnapshot = mSnapshot.save(saveFlags);
-
-        // return the old save count
-        return count;
-    }
-
-    private int saveLayerAlpha(RectF rect, int alpha, int saveFlags) {
-        Paint_Delegate paint = new Paint_Delegate();
-        paint.setAlpha(alpha);
-        return saveLayer(rect, paint, saveFlags);
-    }
-
-    private int saveLayer(RectF rect, Paint_Delegate paint, int saveFlags) {
-        // get the current save count
-        int count = mSnapshot.size();
-
-        mSnapshot = mSnapshot.saveLayer(rect, paint, saveFlags);
-
-        // return the old save count
-        return count;
-    }
-
-    /**
-     * Restores the {@link GcSnapshot} to <var>saveCount</var>
-     * @param saveCount the saveCount
-     */
-    private void restoreTo(int saveCount) {
-        mSnapshot = mSnapshot.restoreTo(saveCount);
-    }
-
-    /**
-     * Restores the top {@link GcSnapshot}
-     */
-    private void restore() {
-        mSnapshot = mSnapshot.restore();
-    }
-
-    private boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
-        return mSnapshot.clipRect(left, top, right, bottom, regionOp);
-    }
-
-    private static void drawBitmap(
-            long nativeCanvas,
-            Bitmap_Delegate bitmap,
-            long nativePaintOrZero,
-            final int sleft, final int stop, final int sright, final int sbottom,
-            final int dleft, final int dtop, final int dright, final int dbottom) {
-        // get the delegate from the native int.
-        Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas);
-        if (canvasDelegate == null) {
-            return;
-        }
-
-        // get the paint, which could be null if the int is 0
-        Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(nativePaintOrZero);
-
-        final BufferedImage image = getImageToDraw(bitmap, paintDelegate, sBoolOut);
-
-        draw(nativeCanvas, nativePaintOrZero, true /*compositeOnly*/, sBoolOut[0],
-                new GcSnapshot.Drawable() {
-                    @Override
-                    public void draw(Graphics2D graphics, Paint_Delegate paint) {
-                        if (paint != null && paint.isFilterBitmap()) {
-                            graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
-                                    RenderingHints.VALUE_INTERPOLATION_BILINEAR);
-                        }
-
-                        //FIXME add support for canvas, screen and bitmap densities.
-                        graphics.drawImage(image, dleft, dtop, dright, dbottom,
-                                sleft, stop, sright, sbottom, null);
-                    }
-        });
-    }
-
-
-    /**
-     * Returns a BufferedImage ready for drawing, based on the bitmap and paint delegate.
-     * The image returns, through a 1-size boolean array, whether the drawing code should
-     * use a SRC composite no matter what the paint says.
-     *
-     * @param bitmap the bitmap
-     * @param paint the paint that will be used to draw
-     * @param forceSrcMode whether the composite will have to be SRC
-     * @return the image to draw
-     */
-    private static BufferedImage getImageToDraw(Bitmap_Delegate bitmap, Paint_Delegate paint,
-            boolean[] forceSrcMode) {
-        BufferedImage image = bitmap.getImage();
-        forceSrcMode[0] = false;
-
-        // if the bitmap config is alpha_8, then we erase all color value from it
-        // before drawing it.
-        if (bitmap.getConfig() == Bitmap.Config.ALPHA_8) {
-            fixAlpha8Bitmap(image);
-        } else if (!bitmap.hasAlpha()) {
-            // hasAlpha is merely a rendering hint. There can in fact be alpha values
-            // in the bitmap but it should be ignored at drawing time.
-            // There is two ways to do this:
-            // - override the composite to be SRC. This can only be used if the composite
-            //   was going to be SRC or SRC_OVER in the first place
-            // - Create a different bitmap to draw in which all the alpha channel values is set
-            //   to 0xFF.
-            if (paint != null) {
-                Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
-                if (xfermodeDelegate instanceof PorterDuffXfermode_Delegate) {
-                    PorterDuff.Mode mode =
-                        ((PorterDuffXfermode_Delegate)xfermodeDelegate).getMode();
-
-                    forceSrcMode[0] = mode == PorterDuff.Mode.SRC_OVER ||
-                            mode == PorterDuff.Mode.SRC;
-                }
-            }
-
-            // if we can't force SRC mode, then create a temp bitmap of TYPE_RGB
-            if (!forceSrcMode[0]) {
-                image = Bitmap_Delegate.createCopy(image, BufferedImage.TYPE_INT_RGB, 0xFF);
-            }
-        }
-
-        return image;
-    }
-
-    private static void fixAlpha8Bitmap(final BufferedImage image) {
-        int w = image.getWidth();
-        int h = image.getHeight();
-        int[] argb = new int[w * h];
-        image.getRGB(0, 0, image.getWidth(), image.getHeight(), argb, 0, image.getWidth());
-
-        final int length = argb.length;
-        for (int i = 0 ; i < length; i++) {
-            argb[i] &= 0xFF000000;
-        }
-        image.setRGB(0, 0, w, h, argb, 0, w);
+        super();
     }
 }
 
diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
index 59ddcc6..a459734 100644
--- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java
@@ -63,16 +63,8 @@
     // ---- native methods ----
 
     @LayoutlibDelegate
-    /*package*/ static long nativeCreate1(long native_shaderA, long native_shaderB,
-            long native_mode) {
-        // FIXME not supported yet.
-        ComposeShader_Delegate newDelegate = new ComposeShader_Delegate();
-        return sManager.addNewDelegate(newDelegate);
-    }
-
-    @LayoutlibDelegate
-    /*package*/ static long nativeCreate2(long native_shaderA, long native_shaderB,
-            int porterDuffMode) {
+    /*package*/ static long nativeCreate(long native_shaderA, long native_shaderB,
+            int native_mode) {
         // FIXME not supported yet.
         ComposeShader_Delegate newDelegate = new ComposeShader_Delegate();
         return sManager.addNewDelegate(newDelegate);
diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
index 0bbe33d..e68d8b3 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java
@@ -90,10 +90,11 @@
     private int mHintingMode = Paint.HINTING_ON;
     private int mHyphenEdit;
     private float mLetterSpacing;  // not used in actual text rendering.
+    private float mWordSpacing;  // not used in actual text rendering.
     // Variant of the font. A paint's variant can only be compact or elegant.
     private FontVariant mFontVariant = FontVariant.COMPACT;
 
-    private Xfermode_Delegate mXfermode;
+    private int mPorterDuffMode = Xfermode.DEFAULT;
     private ColorFilter_Delegate mColorFilter;
     private Shader_Delegate mShader;
     private PathEffect_Delegate mPathEffect;
@@ -206,12 +207,10 @@
     }
 
     /**
-     * Returns the {@link Xfermode} delegate or null if none have been set
-     *
-     * @return the delegate or null.
+     * Returns the {@link PorterDuff.Mode} as an int
      */
-    public Xfermode_Delegate getXfermode() {
-        return mXfermode;
+    public int getPorterDuffMode() {
+        return mPorterDuffMode;
     }
 
     /**
@@ -841,16 +840,12 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static long nSetXfermode(long native_object, long xfermode) {
-        // get the delegate from the native int.
+    /*package*/ static void nSetXfermode(long native_object, int xfermode) {
         Paint_Delegate delegate = sManager.getDelegate(native_object);
         if (delegate == null) {
-            return xfermode;
+            return;
         }
-
-        delegate.mXfermode = Xfermode_Delegate.getDelegate(xfermode);
-
-        return xfermode;
+        delegate.mPorterDuffMode = xfermode;
     }
 
     @LayoutlibDelegate
@@ -998,7 +993,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nGetTextRunCursor(long native_object, char[] text,
+    /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, char[] text,
             int contextStart, int contextLength, int flags, int offset, int cursorOpt) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1007,7 +1002,7 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static int nGetTextRunCursor(long native_object, String text,
+    /*package*/ static int nGetTextRunCursor(Paint paint, long native_object, String text,
             int contextStart, int contextEnd, int flags, int offset, int cursorOpt) {
         // FIXME
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
@@ -1086,6 +1081,26 @@
     }
 
     @LayoutlibDelegate
+    /*package*/ static float nGetWordSpacing(long nativePaint) {
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return 0;
+        }
+        return delegate.mWordSpacing;
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nSetWordSpacing(long nativePaint, float wordSpacing) {
+        Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
+                "Paint.setWordSpacing() not supported.", null, null);
+        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
+        if (delegate == null) {
+            return;
+        }
+        delegate.mWordSpacing = wordSpacing;
+    }
+
+    @LayoutlibDelegate
     /*package*/ static void nSetFontFeatureSettings(long nativePaint, String settings) {
         Bridge.getLog().fidelityWarning(LayoutLog.TAG_TEXT_RENDERING,
                 "Paint.setFontFeatureSettings() not supported.", null, null);
@@ -1215,7 +1230,7 @@
 
         mStrokeWidth = paint.mStrokeWidth;
         mStrokeMiter = paint.mStrokeMiter;
-        mXfermode = paint.mXfermode;
+        mPorterDuffMode = paint.mPorterDuffMode;
         mColorFilter = paint.mColorFilter;
         mShader = paint.mShader;
         mPathEffect = paint.mPathEffect;
@@ -1242,7 +1257,7 @@
         mTextSize = 20.f;
         mTextScaleX = 1.f;
         mTextSkewX = 0.f;
-        mXfermode = null;
+        mPorterDuffMode = Xfermode.DEFAULT;
         mColorFilter = null;
         mShader = null;
         mPathEffect = null;
diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
deleted file mode 100644
index 8825f84..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.layoutlib.bridge.impl.PorterDuffUtility;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.graphics.PorterDuff.Mode;
-
-import java.awt.Composite;
-
-import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode;
-
-/**
- * Delegate implementing the native methods of android.graphics.PorterDuffXfermode
- *
- * Through the layoutlib_create tool, the original native methods of PorterDuffXfermode have been
- * replaced by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original PorterDuffXfermode class.
- *
- * Because this extends {@link Xfermode_Delegate}, there's no need to use a
- * {@link DelegateManager}, as all the PathEffect classes will be added to the manager owned by
- * {@link Xfermode_Delegate}.
- *
- */
-public class PorterDuffXfermode_Delegate extends Xfermode_Delegate {
-
-    // ---- delegate data ----
-
-    private final Mode mMode;
-
-    // ---- Public Helper methods ----
-
-    public Mode getMode() {
-        return mMode;
-    }
-
-    @Override
-    public Composite getComposite(int alpha) {
-        return PorterDuffUtility.getComposite(mMode, alpha);
-    }
-
-    @Override
-    public boolean isSupported() {
-        return true;
-    }
-
-    @Override
-    public String getSupportMessage() {
-        // no message since isSupported returns true;
-        return null;
-    }
-
-
-    // ---- native methods ----
-
-    @LayoutlibDelegate
-    /*package*/ static long nativeCreateXfermode(int mode) {
-        PorterDuffXfermode_Delegate newDelegate = new PorterDuffXfermode_Delegate(mode);
-        return sManager.addNewDelegate(newDelegate);
-    }
-
-    // ---- Private delegate/helper methods ----
-
-    private PorterDuffXfermode_Delegate(int mode) {
-        mMode = getPorterDuffMode(mode);
-    }
-
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java
deleted file mode 100644
index 94a6d76..0000000
--- a/tools/layoutlib/bridge/src/android/graphics/Xfermode_Delegate.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.graphics;
-
-import com.android.layoutlib.bridge.impl.DelegateManager;
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import java.awt.Composite;
-
-/**
- * Delegate implementing the native methods of android.graphics.Xfermode
- *
- * Through the layoutlib_create tool, the original native methods of Xfermode have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- * This class behaves like the original native implementation, but in Java, keeping previously
- * native data into its own objects and mapping them to int that are sent back and forth between
- * it and the original Xfermode class.
- *
- * This also serve as a base class for all Xfermode delegate classes.
- *
- * @see DelegateManager
- *
- */
-public abstract class Xfermode_Delegate {
-
-    // ---- delegate manager ----
-    protected static final DelegateManager<Xfermode_Delegate> sManager =
-            new DelegateManager<Xfermode_Delegate>(Xfermode_Delegate.class);
-
-    // ---- delegate helper data ----
-
-    // ---- delegate data ----
-
-    // ---- Public Helper methods ----
-
-    public static Xfermode_Delegate getDelegate(long native_instance) {
-        return sManager.getDelegate(native_instance);
-    }
-
-    public abstract Composite getComposite(int alpha);
-    public abstract boolean isSupported();
-    public abstract String getSupportMessage();
-
-
-    // ---- native methods ----
-
-    @LayoutlibDelegate
-    /*package*/ static void finalizer(long native_instance) {
-        sManager.removeJavaReferenceFor(native_instance);
-    }
-
-    // ---- Private delegate/helper methods ----
-
-}
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
index 200fe3b..ad2c564 100644
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_Delegate.java
@@ -58,8 +58,13 @@
     }
 
     @LayoutlibDelegate
+    /*package*/ static void nSetVectorDrawableTarget(long animatorPtr, long vectorDrawablePtr) {
+        // TODO: implement
+    }
+    @LayoutlibDelegate
     /*package*/ static void nAddAnimator(long setPtr, long propertyValuesHolder,
-            long nativeInterpolator, long startDelay, long duration, int repeatCount) {
+            long nativeInterpolator, long startDelay, long duration, int repeatCount,
+            int repeatMode) {
         PropertySetter holder = sHolders.getDelegate(propertyValuesHolder);
         if (holder == null || holder.getValues() == null) {
             return;
@@ -72,6 +77,7 @@
         animator.setStartDelay(startDelay);
         animator.setDuration(duration);
         animator.setRepeatCount(repeatCount);
+        animator.setRepeatMode(repeatMode);
         animator.setTarget(holder);
         animator.setPropertyName(holder.getValues().getPropertyName());
 
@@ -137,6 +143,14 @@
     }
 
     @LayoutlibDelegate
+    /*package*/ static void nSetPropertyHolderData(long nativePtr, int[] data, int length) {
+        PropertySetter setter = sHolders.getDelegate(nativePtr);
+        assert setter != null;
+
+        setter.setValues(data);
+    }
+
+    @LayoutlibDelegate
     /*package*/ static void nStart(long animatorSetPtr, VectorDrawableAnimatorRT set, int id) {
         AnimatorSetHolder animatorSet = sAnimatorSets.getDelegate(animatorSetPtr);
         assert animatorSet != null;
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
index 3d78931..fc848d9 100644
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate.java
@@ -18,6 +18,7 @@
 
 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
 
+import android.graphics.Canvas;
 import android.graphics.drawable.AnimatedVectorDrawable.VectorDrawableAnimatorRT;
 
 public class AnimatedVectorDrawable_VectorDrawableAnimatorRT_Delegate {
@@ -25,4 +26,9 @@
     /*package*/ static boolean useLastSeenTarget(VectorDrawableAnimatorRT thisDrawableAnimator) {
         return true;
     }
+
+    @LayoutlibDelegate
+    /*package*/ static void onDraw(VectorDrawableAnimatorRT thisDrawableAnimator, Canvas canvas) {
+        // Do not attempt to record as we are not using a DisplayListCanvas
+    }
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
index 9904263..cee679a 100644
--- a/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/drawable/VectorDrawable_Delegate.java
@@ -23,6 +23,7 @@
 import android.content.res.Resources;
 import android.content.res.Resources.Theme;
 import android.content.res.TypedArray;
+import android.graphics.BaseCanvas_Delegate;
 import android.graphics.Canvas_Delegate;
 import android.graphics.Color;
 import android.graphics.Matrix;
@@ -1197,7 +1198,7 @@
                     fillPaintDelegate.setColorFilter(filterPtr);
                     fillPaintDelegate.setShader(fullPath.mFillGradient);
                     Path_Delegate.native_setFillType(mRenderPath.mNativePath, fullPath.mFillType);
-                    Canvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
+                    BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, fillPaint
                             .getNativeInstance());
                 }
 
@@ -1228,7 +1229,7 @@
                     final float finalStrokeScale = minScale * matrixScale;
                     strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
                     strokePaintDelegate.setShader(fullPath.mStrokeGradient);
-                    Canvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, strokePaint
+                    BaseCanvas_Delegate.nDrawPath(canvasPtr, mRenderPath.mNativePath, strokePaint
                             .getNativeInstance());
                 }
             }
diff --git a/tools/layoutlib/bridge/src/android/os/ServiceManager.java b/tools/layoutlib/bridge/src/android/os/ServiceManager.java
index 549074d..34c7845 100644
--- a/tools/layoutlib/bridge/src/android/os/ServiceManager.java
+++ b/tools/layoutlib/bridge/src/android/os/ServiceManager.java
@@ -31,6 +31,13 @@
     }
 
     /**
+     * Is not supposed to return null, but that is fine for layoutlib.
+     */
+    public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException {
+        throw new ServiceNotFoundException(name);
+    }
+
+    /**
      * Place a new @a service called @a name into the service
      * manager.
      *
@@ -71,4 +78,18 @@
     public static void initServiceCache(Map<String, IBinder> cache) {
         // pass
     }
+
+    /**
+     * Exception thrown when no service published for given name. This might be
+     * thrown early during boot before certain services have published
+     * themselves.
+     *
+     * @hide
+     */
+    public static class ServiceNotFoundException extends Exception {
+        // identical to the original implementation
+        public ServiceNotFoundException(String name) {
+            super("No service published for: " + name);
+        }
+    }
 }
diff --git a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java
index af0c456..d299add 100644
--- a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java
@@ -102,4 +102,9 @@
     /*package*/ static void native_add_change_callback() {
         // pass.
     }
+
+    @LayoutlibDelegate
+    /*package*/ static void native_report_sysprop_change() {
+        // pass.
+    }
 }
diff --git a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
index d15cb48..fad35d2 100644
--- a/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
+++ b/tools/layoutlib/bridge/src/android/view/RectShadowPainter.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import com.android.layoutlib.bridge.impl.GcSnapshot;
 import com.android.layoutlib.bridge.impl.ResourceHelper;
 
 import android.graphics.Canvas;
@@ -32,6 +33,8 @@
 import android.graphics.Region.Op;
 import android.graphics.Shader.TileMode;
 
+import java.awt.Rectangle;
+
 /**
  * Paints shadow for rounded rectangles. Inspiration from CardView. Couldn't use that directly,
  * since it modifies the size of the content, that we can't do.
@@ -127,9 +130,16 @@
         int saved = canvas.save();
         // Usually canvas has been translated to the top left corner of the view when this is
         // called. So, setting a clip rect at 0,0 will clip the top left part of the shadow.
-        // Thus, we just expand in each direction by width and height of the canvas.
-        canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(),
-                canvas.getHeight(), Op.REPLACE);
+        // Thus, we just expand in each direction by width and height of the canvas, while staying
+        // inside the original drawing region.
+        GcSnapshot snapshot = Canvas_Delegate.getDelegate(canvas).getSnapshot();
+        Rectangle originalClip = snapshot.getOriginalClip();
+        if (originalClip != null) {
+            canvas.clipRect(originalClip.x, originalClip.y, originalClip.x + originalClip.width,
+              originalClip.y + originalClip.height, Op.REPLACE);
+            canvas.clipRect(-canvas.getWidth(), -canvas.getHeight(), canvas.getWidth(),
+              canvas.getHeight(), Op.INTERSECT);
+        }
         canvas.translate(0, shadowSize / 2f);
         return saved;
     }
diff --git a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
index 24f7887..a801cb0 100644
--- a/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/RenderNode_Delegate.java
@@ -21,6 +21,8 @@
 
 import android.graphics.Matrix;
 
+import libcore.util.NativeAllocationRegistry_Delegate;
+
 /**
  * Delegate implementing the native methods of {@link RenderNode}
  * <p/>
@@ -35,7 +37,7 @@
     // ---- delegate manager ----
     private static final DelegateManager<RenderNode_Delegate> sManager =
             new DelegateManager<RenderNode_Delegate>(RenderNode_Delegate.class);
-
+    private static long sFinalizer = -1;
 
     private float mLift;
     private float mTranslationX;
@@ -62,8 +64,13 @@
     }
 
     @LayoutlibDelegate
-    /*package*/ static void nDestroyRenderNode(long renderNode) {
-        sManager.removeJavaReferenceFor(renderNode);
+    /*package*/ static long nGetNativeFinalizer() {
+        synchronized (RenderNode_Delegate.class) {
+            if (sFinalizer == -1) {
+                sFinalizer = NativeAllocationRegistry_Delegate.createFinalizer(sManager::removeJavaReferenceFor);
+            }
+        }
+        return sFinalizer;
     }
 
     @LayoutlibDelegate
diff --git a/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java b/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java
new file mode 100644
index 0000000..06874bd
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/view/textservice/TextServicesManager.java
@@ -0,0 +1,342 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.textservice;
+
+import com.android.internal.textservice.ISpellCheckerSessionListener;
+import com.android.internal.textservice.ITextServicesManager;
+import com.android.internal.textservice.ITextServicesSessionListener;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.textservice.SpellCheckerSession.SpellCheckerSessionListener;
+
+import java.util.Locale;
+
+/**
+ * System API to the overall text services, which arbitrates interaction between applications
+ * and text services. You can retrieve an instance of this interface with
+ * {@link Context#getSystemService(String) Context.getSystemService()}.
+ *
+ * The user can change the current text services in Settings. And also applications can specify
+ * the target text services.
+ *
+ * <h3>Architecture Overview</h3>
+ *
+ * <p>There are three primary parties involved in the text services
+ * framework (TSF) architecture:</p>
+ *
+ * <ul>
+ * <li> The <strong>text services manager</strong> as expressed by this class
+ * is the central point of the system that manages interaction between all
+ * other parts.  It is expressed as the client-side API here which exists
+ * in each application context and communicates with a global system service
+ * that manages the interaction across all processes.
+ * <li> A <strong>text service</strong> implements a particular
+ * interaction model allowing the client application to retrieve information of text.
+ * The system binds to the current text service that is in use, causing it to be created and run.
+ * <li> Multiple <strong>client applications</strong> arbitrate with the text service
+ * manager for connections to text services.
+ * </ul>
+ *
+ * <h3>Text services sessions</h3>
+ * <ul>
+ * <li>The <strong>spell checker session</strong> is one of the text services.
+ * {@link android.view.textservice.SpellCheckerSession}</li>
+ * </ul>
+ *
+ */
+public final class TextServicesManager {
+    private static final String TAG = TextServicesManager.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    private static TextServicesManager sInstance;
+
+    private final ITextServicesManager mService;
+
+    private TextServicesManager() {
+        mService = new FakeTextServicesManager();
+    }
+
+    /**
+     * Retrieve the global TextServicesManager instance, creating it if it doesn't already exist.
+     * @hide
+     */
+    public static TextServicesManager getInstance() {
+        synchronized (TextServicesManager.class) {
+            if (sInstance == null) {
+                sInstance = new TextServicesManager();
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Returns the language component of a given locale string.
+     */
+    private static String parseLanguageFromLocaleString(String locale) {
+        final int idx = locale.indexOf('_');
+        if (idx < 0) {
+            return locale;
+        } else {
+            return locale.substring(0, idx);
+        }
+    }
+
+    /**
+     * Get a spell checker session for the specified spell checker
+     * @param locale the locale for the spell checker. If {@code locale} is null and
+     * referToSpellCheckerLanguageSettings is true, the locale specified in Settings will be
+     * returned. If {@code locale} is not null and referToSpellCheckerLanguageSettings is true,
+     * the locale specified in Settings will be returned only when it is same as {@code locale}.
+     * Exceptionally, when referToSpellCheckerLanguageSettings is true and {@code locale} is
+     * only language (e.g. "en"), the specified locale in Settings (e.g. "en_US") will be
+     * selected.
+     * @param listener a spell checker session lister for getting results from a spell checker.
+     * @param referToSpellCheckerLanguageSettings if true, the session for one of enabled
+     * languages in settings will be returned.
+     * @return the spell checker session of the spell checker
+     */
+    public SpellCheckerSession newSpellCheckerSession(Bundle bundle, Locale locale,
+            SpellCheckerSessionListener listener, boolean referToSpellCheckerLanguageSettings) {
+        if (listener == null) {
+            throw new NullPointerException();
+        }
+        if (!referToSpellCheckerLanguageSettings && locale == null) {
+            throw new IllegalArgumentException("Locale should not be null if you don't refer"
+                    + " settings.");
+        }
+
+        if (referToSpellCheckerLanguageSettings && !isSpellCheckerEnabled()) {
+            return null;
+        }
+
+        final SpellCheckerInfo sci;
+        try {
+            sci = mService.getCurrentSpellChecker(null);
+        } catch (RemoteException e) {
+            return null;
+        }
+        if (sci == null) {
+            return null;
+        }
+        SpellCheckerSubtype subtypeInUse = null;
+        if (referToSpellCheckerLanguageSettings) {
+            subtypeInUse = getCurrentSpellCheckerSubtype(true);
+            if (subtypeInUse == null) {
+                return null;
+            }
+            if (locale != null) {
+                final String subtypeLocale = subtypeInUse.getLocale();
+                final String subtypeLanguage = parseLanguageFromLocaleString(subtypeLocale);
+                if (subtypeLanguage.length() < 2 || !locale.getLanguage().equals(subtypeLanguage)) {
+                    return null;
+                }
+            }
+        } else {
+            final String localeStr = locale.toString();
+            for (int i = 0; i < sci.getSubtypeCount(); ++i) {
+                final SpellCheckerSubtype subtype = sci.getSubtypeAt(i);
+                final String tempSubtypeLocale = subtype.getLocale();
+                final String tempSubtypeLanguage = parseLanguageFromLocaleString(tempSubtypeLocale);
+                if (tempSubtypeLocale.equals(localeStr)) {
+                    subtypeInUse = subtype;
+                    break;
+                } else if (tempSubtypeLanguage.length() >= 2 &&
+                        locale.getLanguage().equals(tempSubtypeLanguage)) {
+                    subtypeInUse = subtype;
+                }
+            }
+        }
+        if (subtypeInUse == null) {
+            return null;
+        }
+        final SpellCheckerSession session = new SpellCheckerSession(
+                sci, mService, listener, subtypeInUse);
+        try {
+            mService.getSpellCheckerService(sci.getId(), subtypeInUse.getLocale(),
+                    session.getTextServicesSessionListener(),
+                    session.getSpellCheckerSessionListener(), bundle);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return session;
+    }
+
+    /**
+     * @hide
+     */
+    public SpellCheckerInfo[] getEnabledSpellCheckers() {
+        try {
+            final SpellCheckerInfo[] retval = mService.getEnabledSpellCheckers();
+            if (DBG) {
+                Log.d(TAG, "getEnabledSpellCheckers: " + (retval != null ? retval.length : "null"));
+            }
+            return retval;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public SpellCheckerInfo getCurrentSpellChecker() {
+        try {
+            // Passing null as a locale for ICS
+            return mService.getCurrentSpellChecker(null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void setCurrentSpellChecker(SpellCheckerInfo sci) {
+        try {
+            if (sci == null) {
+                throw new NullPointerException("SpellCheckerInfo is null.");
+            }
+            mService.setCurrentSpellChecker(null, sci.getId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public SpellCheckerSubtype getCurrentSpellCheckerSubtype(
+            boolean allowImplicitlySelectedSubtype) {
+        try {
+            // Passing null as a locale until we support multiple enabled spell checker subtypes.
+            return mService.getCurrentSpellCheckerSubtype(null, allowImplicitlySelectedSubtype);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void setSpellCheckerSubtype(SpellCheckerSubtype subtype) {
+        try {
+            final int hashCode;
+            if (subtype == null) {
+                hashCode = 0;
+            } else {
+                hashCode = subtype.hashCode();
+            }
+            mService.setCurrentSpellCheckerSubtype(null, hashCode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void setSpellCheckerEnabled(boolean enabled) {
+        try {
+            mService.setSpellCheckerEnabled(enabled);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isSpellCheckerEnabled() {
+        try {
+            return mService.isSpellCheckerEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private static class FakeTextServicesManager implements ITextServicesManager {
+
+        @Override
+        public void finishSpellCheckerService(ISpellCheckerSessionListener arg0)
+                throws RemoteException {
+            // TODO Auto-generated method stub
+
+        }
+
+        @Override
+        public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1)
+                throws RemoteException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+        @Override
+        public void getSpellCheckerService(String arg0, String arg1,
+                ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4)
+                throws RemoteException {
+            // TODO Auto-generated method stub
+
+        }
+
+        @Override
+        public boolean isSpellCheckerEnabled() throws RemoteException {
+            // TODO Auto-generated method stub
+            return false;
+        }
+
+        @Override
+        public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException {
+            // TODO Auto-generated method stub
+
+        }
+
+        @Override
+        public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException {
+            // TODO Auto-generated method stub
+
+        }
+
+        @Override
+        public void setSpellCheckerEnabled(boolean arg0) throws RemoteException {
+            // TODO Auto-generated method stub
+
+        }
+
+        @Override
+        public IBinder asBinder() {
+            // TODO Auto-generated method stub
+            return null;
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java
deleted file mode 100644
index 3017292..0000000
--- a/tools/layoutlib/bridge/src/com/android/internal/textservice/ITextServicesManager_Stub_Delegate.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2011 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.internal.textservice;
-
-import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
-
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.view.textservice.SpellCheckerInfo;
-import android.view.textservice.SpellCheckerSubtype;
-
-
-/**
- * Delegate used to provide new implementation of a select few methods of
- * {@link ITextServicesManager$Stub}
- *
- * Through the layoutlib_create tool, the original  methods of Stub have been replaced
- * by calls to methods of the same name in this delegate class.
- *
- */
-public class ITextServicesManager_Stub_Delegate {
-
-    @LayoutlibDelegate
-    public static ITextServicesManager asInterface(IBinder obj) {
-        // ignore the obj and return a fake interface implementation
-        return new FakeTextServicesManager();
-    }
-
-    private static class FakeTextServicesManager implements ITextServicesManager {
-
-        @Override
-        public void finishSpellCheckerService(ISpellCheckerSessionListener arg0)
-                throws RemoteException {
-            // TODO Auto-generated method stub
-
-        }
-
-        @Override
-        public SpellCheckerInfo getCurrentSpellChecker(String arg0) throws RemoteException {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        @Override
-        public SpellCheckerSubtype getCurrentSpellCheckerSubtype(String arg0, boolean arg1)
-                throws RemoteException {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        @Override
-        public SpellCheckerInfo[] getEnabledSpellCheckers() throws RemoteException {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-        @Override
-        public void getSpellCheckerService(String arg0, String arg1,
-                ITextServicesSessionListener arg2, ISpellCheckerSessionListener arg3, Bundle arg4)
-                throws RemoteException {
-            // TODO Auto-generated method stub
-
-        }
-
-        @Override
-        public boolean isSpellCheckerEnabled() throws RemoteException {
-            // TODO Auto-generated method stub
-            return false;
-        }
-
-        @Override
-        public void setCurrentSpellChecker(String arg0, String arg1) throws RemoteException {
-            // TODO Auto-generated method stub
-
-        }
-
-        @Override
-        public void setCurrentSpellCheckerSubtype(String arg0, int arg1) throws RemoteException {
-            // TODO Auto-generated method stub
-
-        }
-
-        @Override
-        public void setSpellCheckerEnabled(boolean arg0) throws RemoteException {
-            // TODO Auto-generated method stub
-
-        }
-
-        @Override
-        public IBinder asBinder() {
-            // TODO Auto-generated method stub
-            return null;
-        }
-
-    }
- }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
index 769ee33..7526e09 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -24,12 +24,13 @@
 import android.graphics.ColorFilter_Delegate;
 import android.graphics.Paint;
 import android.graphics.Paint_Delegate;
+import android.graphics.PorterDuff;
+import android.graphics.PorterDuff.Mode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
 import android.graphics.Region_Delegate;
 import android.graphics.Shader_Delegate;
-import android.graphics.Xfermode_Delegate;
 
 import java.awt.AlphaComposite;
 import java.awt.Color;
@@ -40,6 +41,7 @@
 import java.awt.Shape;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Area;
+import java.awt.geom.NoninvertibleTransformException;
 import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
 import java.util.ArrayList;
@@ -826,28 +828,9 @@
             g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f));
             return;
         }
-        Xfermode_Delegate xfermodeDelegate = paint.getXfermode();
-        if (xfermodeDelegate != null) {
-            if (xfermodeDelegate.isSupported()) {
-                Composite composite = xfermodeDelegate.getComposite(alpha);
-                assert composite != null;
-                if (composite != null) {
-                    g.setComposite(composite);
-                    return;
-                }
-            } else {
-                Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE,
-                        xfermodeDelegate.getSupportMessage(),
-                        null /*throwable*/, null /*data*/);
-            }
-        }
-        // if there was no custom xfermode, but we have alpha (due to a shader and a non
-        // opaque alpha channel in the paint color), then we create an AlphaComposite anyway
-        // that will handle the alpha.
-        if (alpha != 0xFF) {
-            g.setComposite(AlphaComposite.getInstance(
-                    AlphaComposite.SRC_OVER, (float) alpha / 255.f));
-        }
+        Mode mode = PorterDuff.intToMode(paint.getPorterDuffMode());
+        Composite composite = PorterDuffUtility.getComposite(mode, alpha);
+        g.setComposite(composite);
     }
 
     private void mapRect(AffineTransform matrix, RectF dst, RectF src) {
@@ -870,4 +853,33 @@
         dst.bottom = Math.max(Math.max(corners[1], corners[3]), Math.max(corners[5], corners[7]));
     }
 
+    /**
+     * Returns the clip of the oldest snapshot of the stack, appropriately translated to be
+     * expressed in the coordinate system of the latest snapshot.
+     */
+    public Rectangle getOriginalClip() {
+        GcSnapshot originalSnapshot = this;
+        while (originalSnapshot.mPrevious != null) {
+            originalSnapshot = originalSnapshot.mPrevious;
+        }
+        if (originalSnapshot.mLayers.isEmpty()) {
+            return null;
+        }
+        Graphics2D graphics2D = originalSnapshot.mLayers.get(0).getGraphics();
+        Rectangle bounds = graphics2D.getClipBounds();
+        if (bounds == null) {
+            return null;
+        }
+        try {
+            AffineTransform originalTransform =
+                    ((Graphics2D) graphics2D.create()).getTransform().createInverse();
+            AffineTransform latestTransform = getTransform().createInverse();
+            bounds.x += latestTransform.getTranslateX() - originalTransform.getTranslateX();
+            bounds.y += latestTransform.getTranslateY() - originalTransform.getTranslateY();
+        } catch (NoninvertibleTransformException e) {
+            Bridge.getLog().warning(null, "Non invertible transformation", null);
+        }
+        return bounds;
+    }
+
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
index 80d7c68..70e2eb1 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java
@@ -24,14 +24,12 @@
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.PorterDuffColorFilter_Delegate;
-import android.graphics.PorterDuffXfermode_Delegate;
 
 import java.awt.AlphaComposite;
 import java.awt.Composite;
 
 /**
- * Provides various utility methods for {@link PorterDuffColorFilter_Delegate} and {@link
- * PorterDuffXfermode_Delegate}.
+ * Provides various utility methods for {@link PorterDuffColorFilter_Delegate}.
  */
 public final class PorterDuffUtility {
 
diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk
index 8a81d0b..565feb6 100644
--- a/tools/layoutlib/bridge/tests/Android.mk
+++ b/tools/layoutlib/bridge/tests/Android.mk
@@ -17,7 +17,9 @@
 include $(CLEAR_VARS)
 
 # Only compile source java files in this lib.
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := \
+	$(call all-java-files-under, src) \
+	$(call all-java-files-under, res/testApp/MyApplication/src/main/myapplication.widgets)
 LOCAL_JAVA_RESOURCE_DIRS := res
 
 LOCAL_MODULE := layoutlib-tests
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
index 0e788e0c..ef95f83 100644
--- a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/allwidgets.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
index 8f9fa8a2..d3f0f89 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ImageUtils.java
@@ -64,7 +64,7 @@
         double scale = THUMBNAIL_SIZE / (double)maxDimension;
         BufferedImage thumbnail = scale(image, scale, scale);
 
-        InputStream is = ImageUtils.class.getResourceAsStream(relativePath);
+        InputStream is = ImageUtils.class.getClassLoader().getResourceAsStream(relativePath);
         if (is == null) {
             String message = "Unable to load golden thumbnail: " + relativePath + "\n";
             message = saveImageAndAppendMessage(thumbnail, message, relativePath);
@@ -179,7 +179,7 @@
                 g.drawString("Actual", 2 * imageWidth + 10, 20);
             }
 
-            File output = new File(getTempDir(), "delta-" + imageName);
+            File output = new File(getFailureDir(), "delta-" + imageName);
             if (output.exists()) {
                 boolean deleted = output.delete();
                 assertTrue(deleted);
@@ -302,15 +302,16 @@
     }
 
     /**
-     * Temp directory where to write the thumbnails and deltas.
+     * Directory where to write the thumbnails and deltas.
      */
     @NonNull
-    private static File getTempDir() {
-        if (System.getProperty("os.name").equals("Mac OS X")) {
-            return new File("/tmp"); //$NON-NLS-1$
-        }
+    private static File getFailureDir() {
+        String workingDirString = System.getProperty("user.dir");
+        File failureDir = new File(workingDirString, "out/failures");
 
-        return new File(System.getProperty("java.io.tmpdir")); //$NON-NLS-1$
+        //noinspection ResultOfMethodCallIgnored
+        failureDir.mkdirs();
+        return failureDir; //$NON-NLS-1$
     }
 
     /**
@@ -319,7 +320,7 @@
     @NonNull
     private static String saveImageAndAppendMessage(@NonNull BufferedImage image,
             @NonNull String initialMessage, @NonNull String relativePath) throws IOException {
-        File output = new File(getTempDir(), getName(relativePath));
+        File output = new File(getFailureDir(), getName(relativePath));
         if (output.exists()) {
             boolean deleted = output.delete();
             assertTrue(deleted);
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index 5423e87..24cbbca 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -121,7 +121,7 @@
     private static final String PLATFORM_DIR;
     private static final String TEST_RES_DIR;
     /** Location of the app to test inside {@link #TEST_RES_DIR}*/
-    private static final String APP_TEST_DIR = "/testApp/MyApplication";
+    private static final String APP_TEST_DIR = "testApp/MyApplication";
     /** Location of the app's res dir inside {@link #TEST_RES_DIR}*/
     private static final String APP_TEST_RES = APP_TEST_DIR + "/src/main/res";
     private static final String APP_CLASSES_LOCATION =
@@ -138,8 +138,7 @@
 
     // Default class loader with access to the app classes
     private ClassLoader mDefaultClassLoader =
-            new URLClassLoader(new URL[]{this.getClass().getResource(APP_CLASSES_LOCATION)},
-                    getClass().getClassLoader());
+            new ModuleClassLoader(APP_CLASSES_LOCATION, getClass().getClassLoader());
 
     @Rule
     public TestWatcher sRenderMessageWatcher = new TestWatcher() {
@@ -313,7 +312,8 @@
         sFrameworkRepo.loadPublicResources(getLogger());
 
         sProjectResources =
-                new ResourceRepository(new FolderWrapper(TEST_RES_DIR + APP_TEST_RES), false) {
+                new ResourceRepository(new FolderWrapper(TEST_RES_DIR + "/" + APP_TEST_RES),
+                        false) {
             @NonNull
             @Override
             protected ResourceItem createResourceItem(@NonNull String name) {
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java
new file mode 100644
index 0000000..3fac778
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/ModuleClassLoader.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.layoutlib.bridge.intensive;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import libcore.io.Streams;
+
+/**
+ * Module class loader that loads classes from the test project.
+ */
+public class ModuleClassLoader extends ClassLoader {
+    private final Map<String, Class<?>> mClasses = new HashMap<>();
+    private String myModuleRoot;
+
+    /**
+     * @param moduleRoot The path to the module root
+     * @param parent The parent class loader
+     */
+    public ModuleClassLoader(String moduleRoot, ClassLoader parent) {
+        super(parent);
+        myModuleRoot = moduleRoot + (moduleRoot.endsWith("/") ? "" : "/");
+    }
+
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        try {
+            return super.findClass(name);
+        } catch (ClassNotFoundException ignored) {
+        }
+
+        Class<?> clazz = mClasses.get(name);
+        if (clazz == null) {
+            String path = name.replace('.', '/').concat(".class");
+            try {
+                byte[] b = Streams.readFully(getResourceAsStream(myModuleRoot + path));
+                clazz = defineClass(name, b, 0, b.length);
+                mClasses.put(name, clazz);
+            } catch (IOException ignore) {
+                throw new ClassNotFoundException(name + " not found");
+            }
+        }
+
+        return clazz;
+    }
+}
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java
index 1110494..bc8083f 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java
@@ -42,9 +42,11 @@
      * @param layoutPath Must start with '/' and be relative to test resources.
      */
     public LayoutPullParser(String layoutPath) {
-        assert layoutPath.startsWith("/");
+        if (layoutPath.startsWith("/")) {
+            layoutPath = layoutPath.substring(1);
+        }
         try {
-            init(getClass().getResourceAsStream(layoutPath));
+            init(getClass().getClassLoader().getResourceAsStream(layoutPath));
         } catch (XmlPullParserException e) {
             throw new IOError(e);
         }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index bf61f7e..a23bad6 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -180,6 +180,7 @@
         "android.graphics.BitmapFactory#finishDecode",
         "android.graphics.BitmapFactory#setDensityFromOptions",
         "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#useLastSeenTarget",
+        "android.graphics.drawable.AnimatedVectorDrawable$VectorDrawableAnimatorRT#onDraw",
         "android.graphics.drawable.GradientDrawable#buildRing",
         "android.graphics.FontFamily#addFont",
         "android.graphics.Typeface#getSystemFontConfigLocation",
@@ -206,7 +207,7 @@
         "android.view.MenuInflater#registerMenu",
         "android.view.RenderNode#getMatrix",
         "android.view.RenderNode#nCreate",
-        "android.view.RenderNode#nDestroyRenderNode",
+        "android.view.RenderNode#nGetNativeFinalizer",
         "android.view.RenderNode#nSetElevation",
         "android.view.RenderNode#nGetElevation",
         "android.view.RenderNode#nSetTranslationX",
@@ -234,7 +235,6 @@
         "android.view.ViewGroup#drawChild",
         "com.android.internal.view.menu.MenuBuilder#createNewMenuItem",
         "com.android.internal.util.XmlUtils#convertValueToInt",
-        "com.android.internal.textservice.ITextServicesManager$Stub#asInterface",
         "dalvik.system.VMRuntime#newUnpaddedArray",
         "libcore.io.MemoryMappedFile#mmapRO",
         "libcore.io.MemoryMappedFile#close",
@@ -247,6 +247,7 @@
      */
     public final static String[] DELEGATE_CLASS_NATIVES = new String[] {
         "android.animation.PropertyValuesHolder",
+        "android.graphics.BaseCanvas",
         "android.graphics.Bitmap",
         "android.graphics.BitmapFactory",
         "android.graphics.BitmapShader",
@@ -275,7 +276,6 @@
         "android.graphics.PathEffect",
         "android.graphics.PathMeasure",
         "android.graphics.PorterDuffColorFilter",
-        "android.graphics.PorterDuffXfermode",
         "android.graphics.RadialGradient",
         "android.graphics.Rasterizer",
         "android.graphics.Region",
@@ -283,7 +283,6 @@
         "android.graphics.SumPathEffect",
         "android.graphics.SweepGradient",
         "android.graphics.Typeface",
-        "android.graphics.Xfermode",
         "android.graphics.drawable.AnimatedVectorDrawable",
         "android.graphics.drawable.VectorDrawable",
         "android.os.SystemClock",
@@ -304,6 +303,7 @@
     private final static String[] RENAMED_CLASSES =
         new String[] {
             "android.os.ServiceManager",                       "android.os._Original_ServiceManager",
+            "android.view.textservice.TextServicesManager",    "android.view.textservice._Original_TextServicesManager",
             "android.util.LruCache",                           "android.util._Original_LruCache",
             "android.view.SurfaceView",                        "android.view._Original_SurfaceView",
             "android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager",
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index 9bb91e5..4b6cf43 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -124,6 +124,7 @@
                         "android.annotation.NonNull",       // annotations
                         "android.annotation.Nullable",      // annotations
                         "com.android.internal.transition.EpicenterTranslateClipReveal",
+                        "com.android.internal.graphics.drawable.AnimationScaleListDrawable",
                     },
                     excludeClasses,
                     new String[] {
diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk
index dafb9c6..61e381d 100644
--- a/tools/layoutlib/create/tests/Android.mk
+++ b/tools/layoutlib/create/tests/Android.mk
@@ -23,7 +23,7 @@
 LOCAL_MODULE := layoutlib-create-tests
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_JAVA_LIBRARIES := layoutlib_create junit
+LOCAL_JAVA_LIBRARIES := layoutlib_create junit-host
 LOCAL_STATIC_JAVA_LIBRARIES := asm-5.0
 
 include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/tools/preload2/Android.mk b/tools/preload2/Android.mk
index 09d95ff..769db6b 100644
--- a/tools/preload2/Android.mk
+++ b/tools/preload2/Android.mk
@@ -12,7 +12,7 @@
 
 # For JDWP access we use the framework in the JDWP tests from Apache Harmony, for
 # convenience (and to not depend on internal JDK APIs).
-LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit
+LOCAL_STATIC_JAVA_LIBRARIES += apache-harmony-jdwp-tests-host junit-host
 
 LOCAL_MODULE:= preload2
 
diff --git a/tools/preload2/src/com/android/preload/Main.java b/tools/preload2/src/com/android/preload/Main.java
index ca5b0e0..cc54a8d 100644
--- a/tools/preload2/src/com/android/preload/Main.java
+++ b/tools/preload2/src/com/android/preload/Main.java
@@ -32,7 +32,8 @@
 import com.android.preload.classdataretrieval.ClassDataRetriever;
 import com.android.preload.classdataretrieval.hprof.Hprof;
 import com.android.preload.classdataretrieval.jdwp.JDWPClassDataRetriever;
-import com.android.preload.ui.UI;
+import com.android.preload.ui.IUI;
+import com.android.preload.ui.SwingUI;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -66,7 +67,7 @@
     private DumpTableModel dataTableModel;
     private DefaultListModel<Client> clientListModel;
 
-    private UI ui;
+    private IUI ui;
 
     // Actions that need to be updated once a device is selected.
     private Collection<DeviceSpecific> deviceSpecificActions;
@@ -89,13 +90,15 @@
      * @param args
      */
     public static void main(String[] args) {
-        Main m = new Main();
+        Main m = new Main(new SwingUI());
         top = m;
 
         m.startUp();
     }
 
-    public Main() {
+    public Main(IUI ui) {
+        this.ui = ui;
+
         clientListModel = new DefaultListModel<Client>();
         dataTableModel = new DumpTableModel();
 
@@ -124,11 +127,10 @@
             }
         }
 
-        ui = new UI(clientListModel, dataTableModel, actions);
-        ui.setVisible(true);
+        ui.prepare(clientListModel, dataTableModel, actions);
     }
 
-    public static UI getUI() {
+    public static IUI getUI() {
         return top.ui;
     }
 
@@ -176,6 +178,7 @@
         new ReloadListAction(clientUtils, getDevice(), clientListModel).run();
 
         getUI().hideWaitDialog();
+        getUI().ready();
     }
 
     private void initDevice() {
diff --git a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
index fbf83d2..5787d85 100644
--- a/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
+++ b/tools/preload2/src/com/android/preload/actions/AbstractThreadedAction.java
@@ -16,6 +16,7 @@
 
 package com.android.preload.actions;
 
+import com.android.preload.Main;
 import java.awt.event.ActionEvent;
 
 import javax.swing.AbstractAction;
@@ -28,7 +29,11 @@
 
     @Override
     public void actionPerformed(ActionEvent e) {
-        new Thread(this).start();
+        if (Main.getUI().isSingleThreaded()) {
+            run();
+        } else {
+            new Thread(this).start();
+        }
     }
 
 }
diff --git a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
index b524716..3a7f7f7 100644
--- a/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
+++ b/tools/preload2/src/com/android/preload/actions/ComputeThresholdAction.java
@@ -32,14 +32,13 @@
 import java.util.regex.Pattern;
 
 import javax.swing.AbstractAction;
-import javax.swing.JFileChooser;
 
 /**
  * Compute an intersection of classes from the given data. A class is in the intersection if it
  * appears in at least the number of threshold given packages. An optional blacklist can be
  * used to filter classes from the intersection.
  */
-public class ComputeThresholdAction extends AbstractAction implements Runnable {
+public class ComputeThresholdAction extends AbstractThreadedAction {
     protected int threshold;
     private Pattern blacklist;
     private DumpTableModel dataTableModel;
@@ -72,7 +71,7 @@
             return;
         }
 
-        new Thread(this).start();
+        super.actionPerformed(e);
     }
 
     @Override
@@ -92,10 +91,8 @@
         boolean ret = Main.getUI().showConfirmDialog("Computed a set with " + result.size()
                 + " classes, would you like to save to disk?", "Save?");
         if (ret) {
-            JFileChooser jfc = new JFileChooser();
-            int ret2 = jfc.showSaveDialog(Main.getUI());
-            if (ret2 == JFileChooser.APPROVE_OPTION) {
-                File f = jfc.getSelectedFile();
+            File f = Main.getUI().showSaveDialog();
+            if (f != null) {
                 saveSet(result, f);
             }
         }
diff --git a/tools/preload2/src/com/android/preload/actions/ExportAction.java b/tools/preload2/src/com/android/preload/actions/ExportAction.java
index cb8b3df..848a568 100644
--- a/tools/preload2/src/com/android/preload/actions/ExportAction.java
+++ b/tools/preload2/src/com/android/preload/actions/ExportAction.java
@@ -19,14 +19,11 @@
 import com.android.preload.DumpDataIO;
 import com.android.preload.DumpTableModel;
 import com.android.preload.Main;
-
 import java.awt.event.ActionEvent;
 import java.io.File;
 import java.io.PrintWriter;
 
-import javax.swing.AbstractAction;
-
-public class ExportAction extends AbstractAction implements Runnable {
+public class ExportAction extends AbstractThreadedAction {
     private File lastSaveFile;
     private DumpTableModel dataTableModel;
 
@@ -39,7 +36,7 @@
     public void actionPerformed(ActionEvent e) {
         lastSaveFile = Main.getUI().showSaveDialog();
         if (lastSaveFile != null) {
-            new Thread(this).start();
+            super.actionPerformed(e);
         }
     }
 
diff --git a/tools/preload2/src/com/android/preload/actions/ImportAction.java b/tools/preload2/src/com/android/preload/actions/ImportAction.java
index 5c19765..bfeeb83 100644
--- a/tools/preload2/src/com/android/preload/actions/ImportAction.java
+++ b/tools/preload2/src/com/android/preload/actions/ImportAction.java
@@ -27,7 +27,7 @@
 
 import javax.swing.AbstractAction;
 
-public class ImportAction extends AbstractAction implements Runnable {
+public class ImportAction extends AbstractThreadedAction {
     private File[] lastOpenFiles;
     private DumpTableModel dataTableModel;
 
@@ -40,7 +40,7 @@
     public void actionPerformed(ActionEvent e) {
         lastOpenFiles = Main.getUI().showOpenDialog(true);
         if (lastOpenFiles != null) {
-            new Thread(this).start();
+            super.actionPerformed(e);
         }
     }
 
diff --git a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
index 385e857..29464fc 100644
--- a/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
+++ b/tools/preload2/src/com/android/preload/actions/RunMonkeyAction.java
@@ -58,7 +58,12 @@
         if (packages.isEmpty()) {
             packages = DEFAULT_MONKEY_PACKAGES;
         }
-        new Thread(new RunMonkeyRunnable(packages)).start();
+        Runnable r = new RunMonkeyRunnable(packages);
+        if (Main.getUI().isSingleThreaded()) {
+            r.run();
+        } else {
+            new Thread(r).start();
+        }
     }
 
     private class RunMonkeyRunnable implements Runnable {
diff --git a/tools/preload2/src/com/android/preload/ui/IUI.java b/tools/preload2/src/com/android/preload/ui/IUI.java
new file mode 100644
index 0000000..9371463
--- /dev/null
+++ b/tools/preload2/src/com/android/preload/ui/IUI.java
@@ -0,0 +1,45 @@
+package com.android.preload.ui;
+
+import com.android.ddmlib.Client;
+import java.io.File;
+import java.util.List;
+import javax.swing.Action;
+import javax.swing.ListModel;
+import javax.swing.table.TableModel;
+
+/**
+ * UI abstraction for the tool. This allows a graphical mode, command line mode,
+ * or silent mode.
+ */
+public interface IUI {
+
+    void prepare(ListModel<Client> clientListModel, TableModel dataTableModel,
+            List<Action> actions);
+
+    void ready();
+
+    boolean isSingleThreaded();
+
+    Client getSelectedClient();
+
+    int getSelectedDataTableRow();
+
+    void showWaitDialog();
+
+    void updateWaitDialog(String s);
+
+    void hideWaitDialog();
+
+    void showMessageDialog(String s);
+
+    boolean showConfirmDialog(String title, String message);
+
+    String showInputDialog(String message);
+
+    <T> T showChoiceDialog(String title, String message, T[] choices);
+
+    File showSaveDialog();
+
+    File[] showOpenDialog(boolean multi);
+
+}
diff --git a/tools/preload2/src/com/android/preload/ui/UI.java b/tools/preload2/src/com/android/preload/ui/SwingUI.java
similarity index 93%
rename from tools/preload2/src/com/android/preload/ui/UI.java
rename to tools/preload2/src/com/android/preload/ui/SwingUI.java
index 47174dd..cab3744 100644
--- a/tools/preload2/src/com/android/preload/ui/UI.java
+++ b/tools/preload2/src/com/android/preload/ui/SwingUI.java
@@ -41,7 +41,7 @@
 import javax.swing.SwingUtilities;
 import javax.swing.table.TableModel;
 
-public class UI extends JFrame {
+public class SwingUI extends JFrame implements IUI {
 
     private JList<Client> clientList;
     private JTable dataTable;
@@ -49,11 +49,18 @@
     // Shared file chooser, means the directory is retained.
     private JFileChooser jfc;
 
-    public UI(ListModel<Client> clientListModel,
-              TableModel dataTableModel,
-              List<Action> actions) {
+    public SwingUI() {
         super("Preloaded-classes computation");
+    }
 
+    @Override
+    public boolean isSingleThreaded() {
+        return false;
+    }
+
+    @Override
+    public void prepare(ListModel<Client> clientListModel, TableModel dataTableModel,
+            List<Action> actions) {
         getContentPane().add(new JScrollPane(clientList = new JList<Client>(clientListModel)),
                 BorderLayout.WEST);
         clientList.setCellRenderer(new ClientListCellRenderer());
@@ -74,18 +81,27 @@
 
         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
         setBounds(100, 100, 800, 600);
+
+        setVisible(true);
     }
 
+    @Override
+    public void ready() {
+    }
+
+    @Override
     public Client getSelectedClient() {
         return clientList.getSelectedValue();
     }
 
+    @Override
     public int getSelectedDataTableRow() {
         return dataTable.getSelectedRow();
     }
 
     private JDialog currentWaitDialog = null;
 
+    @Override
     public void showWaitDialog() {
         if (currentWaitDialog == null) {
             currentWaitDialog = new JDialog(this, "Please wait...", true);
@@ -111,6 +127,7 @@
         });
     }
 
+    @Override
     public void updateWaitDialog(String s) {
         if (currentWaitDialog != null) {
             ((JLabel) currentWaitDialog.getContentPane().getComponent(0)).setText(s);
@@ -124,6 +141,7 @@
         }
     }
 
+    @Override
     public void hideWaitDialog() {
         if (currentWaitDialog != null) {
             currentWaitDialog.setVisible(false);
@@ -131,6 +149,7 @@
         }
     }
 
+    @Override
     public void showMessageDialog(String s) {
         // Hide the wait dialog...
         if (currentWaitDialog != null) {
@@ -147,6 +166,7 @@
         }
     }
 
+    @Override
     public boolean showConfirmDialog(String title, String message) {
         // Hide the wait dialog...
         if (currentWaitDialog != null) {
@@ -164,6 +184,7 @@
         }
     }
 
+    @Override
     public String showInputDialog(String message) {
         // Hide the wait dialog...
         if (currentWaitDialog != null) {
@@ -180,6 +201,7 @@
         }
     }
 
+    @Override
     @SuppressWarnings("unchecked")
     public <T> T showChoiceDialog(String title, String message, T[] choices) {
         // Hide the wait dialog...
@@ -203,6 +225,7 @@
         }
     }
 
+    @Override
     public File showSaveDialog() {
         // Hide the wait dialog...
         if (currentWaitDialog != null) {
@@ -228,6 +251,7 @@
         }
     }
 
+    @Override
     public File[] showOpenDialog(boolean multi) {
         // Hide the wait dialog...
         if (currentWaitDialog != null) {
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index a62a0fb..643753a 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -36,6 +36,27 @@
     public HomeSP homeSp = null;
     public Credential credential = null;
 
+    /**
+     * Constructor for creating PasspointConfiguration with default values.
+     */
+    public PasspointConfiguration() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public PasspointConfiguration(PasspointConfiguration source) {
+        if (source != null) {
+            if (source.homeSp != null) {
+                homeSp = new HomeSP(source.homeSp);
+            }
+            if (source.credential != null) {
+                credential = new Credential(source.credential);
+            }
+        }
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 57e65eb..790dfaf 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -109,6 +109,25 @@
          */
         public String nonEapInnerMethod = null;
 
+        /**
+         * Constructor for creating UserCredential with default values.
+         */
+        public UserCredential() {}
+
+        /**
+         * Copy constructor.
+         *
+         * @param source The source to copy from
+         */
+        public UserCredential(UserCredential source) {
+            if (source != null) {
+                username = source.username;
+                password = source.password;
+                eapType = source.eapType;
+                nonEapInnerMethod = source.nonEapInnerMethod;
+            }
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -221,6 +240,26 @@
          */
         public byte[] certSha256FingerPrint = null;
 
+        /**
+         * Constructor for creating CertificateCredential with default values.
+         */
+        public CertificateCredential() {}
+
+        /**
+         * Copy constructor.
+         *
+         * @param source The source to copy from
+         */
+        public CertificateCredential(CertificateCredential source) {
+            if (source != null) {
+                certType = source.certType;
+                if (source.certSha256FingerPrint != null) {
+                    certSha256FingerPrint = Arrays.copyOf(source.certSha256FingerPrint,
+                                                          source.certSha256FingerPrint.length);
+                }
+            }
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -307,6 +346,23 @@
          */
         public int eapType = Integer.MIN_VALUE;
 
+        /**
+         * Constructor for creating SimCredential with default values.
+         */
+        public SimCredential() {}
+
+        /**
+         * Copy constructor
+         *
+         * @param source The source to copy from
+         */
+        public SimCredential(SimCredential source) {
+            if (source != null) {
+                imsi = source.imsi;
+                eapType = source.eapType;
+            }
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -422,6 +478,37 @@
      */
     public PrivateKey clientPrivateKey = null;
 
+    /**
+     * Constructor for creating Credential with default values.
+     */
+    public Credential() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public Credential(Credential source) {
+        if (source != null) {
+            realm = source.realm;
+            if (source.userCredential != null) {
+                userCredential = new UserCredential(source.userCredential);
+            }
+            if (source.certCredential != null) {
+                certCredential = new CertificateCredential(source.certCredential);
+            }
+            if (source.simCredential != null) {
+                simCredential = new SimCredential(source.simCredential);
+            }
+            if (source.clientCertificateChain != null) {
+                clientCertificateChain = Arrays.copyOf(source.clientCertificateChain,
+                                                       source.clientCertificateChain.length);
+            }
+            caCertificate = source.caCertificate;
+            clientPrivateKey = source.clientPrivateKey;
+        }
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
index 5837c06..d4a5792 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java
@@ -53,6 +53,27 @@
      */
     public long[] roamingConsortiumOIs = null;
 
+    /**
+     * Constructor for creating HomeSP with default values.
+     */
+    public HomeSP() {}
+
+    /**
+     * Copy constructor.
+     *
+     * @param source The source to copy from
+     */
+    public HomeSP(HomeSP source) {
+        if (source != null) {
+            fqdn = source.fqdn;
+            friendlyName = source.friendlyName;
+            if (source.roamingConsortiumOIs != null) {
+                roamingConsortiumOIs = Arrays.copyOf(source.roamingConsortiumOIs,
+                                                     source.roamingConsortiumOIs.length);
+            }
+        }
+    }
+
     @Override
     public int describeContents() {
         return 0;
diff --git a/wifi/tests/src/android/net/wifi/aware/TlvBufferUtilsTest.java b/wifi/tests/src/android/net/wifi/aware/TlvBufferUtilsTest.java
new file mode 100644
index 0000000..4b6957b
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/aware/TlvBufferUtilsTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+
+/**
+ * Unit test harness for TlvBufferUtils class.
+ */
+@SmallTest
+public class TlvBufferUtilsTest {
+    @Rule
+    public ErrorCollector collector = new ErrorCollector();
+
+    /*
+     * TlvBufferUtils Tests
+     */
+
+    @Test
+    public void testTlvBuild() {
+        TlvBufferUtils.TlvConstructor tlv11 = new TlvBufferUtils.TlvConstructor(1, 1);
+        tlv11.allocate(15);
+        tlv11.putByte(0, (byte) 2);
+        tlv11.putByteArray(2, new byte[] {
+                0, 1, 2 });
+
+        collector.checkThat("tlv11-correct-construction",
+                tlv11.getArray(), equalTo(new byte[]{0, 1, 2, 2, 3, 0, 1, 2}));
+
+        LvBufferUtils.LvConstructor tlv01 = new LvBufferUtils.LvConstructor(1);
+        tlv01.allocate(15);
+        tlv01.putByte((byte) 2);
+        tlv01.putByteArray(2, new byte[] {
+                0, 1, 2 });
+
+        collector.checkThat("tlv01-correct-construction",
+                tlv01.getArray(), equalTo(new byte[] {1, 2, 3, 0, 1, 2 }));
+
+        collector.checkThat("tlv11-valid",
+                TlvBufferUtils.isValid(tlv11.getArray(), 1, 1),
+                equalTo(true));
+        collector.checkThat("tlv01-valid",
+                LvBufferUtils.isValid(tlv01.getArray(), 1),
+                equalTo(true));
+    }
+
+    @Test
+    public void testTlvIterate() {
+        final String ascii = "ABC";
+        final String nonAscii = "何かもっと複雑な";
+
+        TlvBufferUtils.TlvConstructor tlv22 = new TlvBufferUtils.TlvConstructor(2, 2);
+        tlv22.allocate(18);
+        tlv22.putInt(0, 2);
+        tlv22.putShort(2, (short) 3);
+        tlv22.putZeroLengthElement(55);
+
+        TlvBufferUtils.TlvIterable tlv22It = new TlvBufferUtils.TlvIterable(2, 2, tlv22.getArray());
+        int count = 0;
+        for (TlvBufferUtils.TlvElement tlv : tlv22It) {
+            if (count == 0) {
+                collector.checkThat("tlv22-correct-iteration-mType", tlv.type, equalTo(0));
+                collector.checkThat("tlv22-correct-iteration-mLength", tlv.length, equalTo(4));
+                collector.checkThat("tlv22-correct-iteration-DATA", tlv.getInt(), equalTo(2));
+            } else if (count == 1) {
+                collector.checkThat("tlv22-correct-iteration-mType", tlv.type, equalTo(2));
+                collector.checkThat("tlv22-correct-iteration-mLength", tlv.length, equalTo(2));
+                collector.checkThat("tlv22-correct-iteration-DATA", (int) tlv.getShort(),
+                        equalTo(3));
+            } else if (count == 2) {
+                collector.checkThat("tlv22-correct-iteration-mType", tlv.type, equalTo(55));
+                collector.checkThat("tlv22-correct-iteration-mLength", tlv.length, equalTo(0));
+            } else {
+                collector.checkThat("Invalid number of iterations in loop - tlv22", true,
+                        equalTo(false));
+            }
+            ++count;
+        }
+        if (count != 3) {
+            collector.checkThat("Invalid number of iterations outside loop - tlv22", true,
+                    equalTo(false));
+        }
+
+        TlvBufferUtils.TlvConstructor tlv02 = new TlvBufferUtils.TlvConstructor(0, 2);
+        tlv02.allocate(100);
+        tlv02.putByte(0, (byte) 2);
+        tlv02.putString(0, ascii);
+        tlv02.putString(0, nonAscii);
+
+        TlvBufferUtils.TlvIterable tlv02It = new TlvBufferUtils.TlvIterable(0, 2, tlv02.getArray());
+        count = 0;
+        for (TlvBufferUtils.TlvElement tlv : tlv02It) {
+            if (count == 0) {
+                collector.checkThat("tlv02-correct-iteration-mLength", tlv.length, equalTo(1));
+                collector.checkThat("tlv02-correct-iteration-DATA", (int) tlv.getByte(),
+                        equalTo(2));
+            } else if (count == 1) {
+                collector.checkThat("tlv02-correct-iteration-mLength", tlv.length,
+                        equalTo(ascii.length()));
+                collector.checkThat("tlv02-correct-iteration-DATA", tlv.getString().equals(ascii),
+                        equalTo(true));
+            } else if (count == 2) {
+                collector.checkThat("tlv02-correct-iteration-mLength", tlv.length,
+                        equalTo(nonAscii.getBytes().length));
+                collector.checkThat("tlv02-correct-iteration-DATA",
+                        tlv.getString().equals(nonAscii), equalTo(true));
+            } else {
+                collector.checkThat("Invalid number of iterations in loop - tlv02", true,
+                        equalTo(false));
+            }
+            ++count;
+        }
+        collector.checkThat("Invalid number of iterations outside loop - tlv02", count,
+                equalTo(3));
+
+        collector.checkThat("tlv22-valid",
+                TlvBufferUtils.isValid(tlv22.getArray(), 2, 2),
+                equalTo(true));
+        collector.checkThat("tlv02-valid",
+                LvBufferUtils.isValid(tlv02.getArray(), 2),
+                equalTo(true));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTlvInvalidSizeT1L0() {
+        TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, 0);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTlvInvalidSizeTm3L2() {
+        TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(-3, 2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTlvInvalidSizeT1Lm2() {
+        TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, -2);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTlvInvalidSizeT1L3() {
+        TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(1, 3);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTlvInvalidSizeT3L1() {
+        TlvBufferUtils.TlvConstructor tlv10 = new TlvBufferUtils.TlvConstructor(3, 1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTlvItInvalidSizeT1L0() {
+        final byte[] dummy = {
+                0, 1, 2 };
+        final int dummyLength = 3;
+        TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(1, 0, dummy);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTlvItInvalidSizeTm3L2() {
+        final byte[] dummy = {
+                0, 1, 2 };
+        final int dummyLength = 3;
+        TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(-3, 2, dummy);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTlvItInvalidSizeT1Lm2() {
+        final byte[] dummy = {
+                0, 1, 2 };
+        final int dummyLength = 3;
+        TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(1, -2, dummy);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTlvItInvalidSizeT1L3() {
+        final byte[] dummy = {
+                0, 1, 2 };
+        final int dummyLength = 3;
+        TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(1, 3, dummy);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testTlvItInvalidSizeT3L1() {
+        final byte[] dummy = {
+                0, 1, 2 };
+        final int dummyLength = 3;
+        TlvBufferUtils.TlvIterable tlvIt10 = new TlvBufferUtils.TlvIterable(3, 1, dummy);
+    }
+
+    /**
+     * Validate that a malformed byte array fails the TLV validity test.
+     */
+    @Test
+    public void testTlvInvalidByteArray() {
+        LvBufferUtils.LvConstructor tlv01 = new LvBufferUtils.LvConstructor(1);
+        tlv01.allocate(15);
+        tlv01.putByte((byte) 2);
+        tlv01.putByteArray(2, new byte[]{0, 1, 2});
+
+        byte[] array = tlv01.getArray();
+        array[0] = 10;
+
+        collector.checkThat("tlv01-invalid",
+                LvBufferUtils.isValid(array, 1), equalTo(false));
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
new file mode 100644
index 0000000..b30ecf7
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -0,0 +1,1054 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.wifi.aware;
+
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.wifi.RttManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.test.TestLooper;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Base64;
+
+import libcore.util.HexEncoding;
+
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test harness for WifiAwareManager class.
+ */
+@SmallTest
+public class WifiAwareManagerTest {
+    private WifiAwareManager mDut;
+    private TestLooper mMockLooper;
+    private Handler mMockLooperHandler;
+
+    @Rule
+    public ErrorCollector collector = new ErrorCollector();
+
+    @Mock
+    public Context mockContext;
+
+    @Mock
+    public WifiAwareAttachCallback mockCallback;
+
+    @Mock
+    public WifiAwareDiscoverySessionCallback mockSessionCallback;
+
+    @Mock
+    public IWifiAwareManager mockAwareService;
+
+    @Mock
+    public WifiAwarePublishDiscoverySession mockPublishSession;
+
+    @Mock
+    public WifiAwareSubscribeDiscoverySession mockSubscribeSession;
+
+    @Mock
+    public RttManager.RttListener mockRttListener;
+
+    private static final int AWARE_STATUS_ERROR = -1;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mDut = new WifiAwareManager(mockContext, mockAwareService);
+        mMockLooper = new TestLooper();
+        mMockLooperHandler = new Handler(mMockLooper.getLooper());
+    }
+
+    /*
+     * Straight pass-through tests
+     */
+
+    /**
+     * Validate pass-through of enableUsage() API.
+     */
+    @Test
+    public void testEnableUsage() throws Exception {
+        mDut.enableUsage();
+
+        verify(mockAwareService).enableUsage();
+    }
+
+    /**
+     * Validate pass-through of disableUsage() API.
+     */
+    @Test
+    public void testDisableUsage() throws Exception {
+        mDut.disableUsage();
+
+        verify(mockAwareService).disableUsage();
+    }
+
+    /**
+     * Validate pass-through of isUsageEnabled() API.
+     */
+    @Test
+    public void testIsUsageEnable() throws Exception {
+        mDut.isAvailable();
+
+        verify(mockAwareService).isUsageEnabled();
+    }
+
+    /**
+     * Validate pass-through of getCharacteristics() API.
+     */
+    @Test
+    public void testGetCharacteristics() throws Exception {
+        mDut.getCharacteristics();
+
+        verify(mockAwareService).getCharacteristics();
+    }
+
+    /*
+     * WifiAwareEventCallbackProxy Tests
+     */
+
+    /**
+     * Validate the successful connect flow: (1) connect + success (2) publish, (3) disconnect
+     * (4) try publishing on old session (5) connect again
+     */
+    @Test
+    public void testConnectFlow() throws Exception {
+        final int clientId = 4565;
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IBinder> binder = ArgumentCaptor.forClass(IBinder.class);
+
+        // (1) connect + success
+        mDut.attach(mockCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).connect(binder.capture(), anyString(),
+                clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+        WifiAwareSession session = sessionCaptor.getValue();
+
+        // (2) publish - should succeed
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+        session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+                any(IWifiAwareDiscoverySessionCallback.class));
+
+        // (3) disconnect
+        session.destroy();
+        inOrder.verify(mockAwareService).disconnect(eq(clientId), eq(binder.getValue()));
+
+        // (4) try publishing again - fails silently
+        session.publish(new PublishConfig.Builder().build(), mockSessionCallback,
+                mMockLooperHandler);
+
+        // (5) connect
+        mDut.attach(mockCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).connect(binder.capture(), anyString(),
+                any(IWifiAwareEventCallback.class), (ConfigRequest) isNull(), eq(false));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService);
+    }
+
+    /**
+     * Validate the failed connect flow: (1) connect + failure, (2) connect + success (3) subscribe
+     */
+    @Test
+    public void testConnectFailure() throws Exception {
+        final int clientId = 4565;
+        final int reason = AWARE_STATUS_ERROR;
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService);
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+
+        // (1) connect + failure
+        mDut.attach(mockCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
+                clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+        clientProxyCallback.getValue().onConnectFail(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttachFailed();
+
+        // (2) connect + success
+        mDut.attach(mockCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
+                clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+        WifiAwareSession session = sessionCaptor.getValue();
+
+        // (4) subscribe: should succeed
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+        session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
+                any(IWifiAwareDiscoverySessionCallback.class));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService);
+    }
+
+    /**
+     * Validate that can call connect to create multiple sessions: (1) connect
+     * + success, (2) try connect again
+     */
+    @Test
+    public void testInvalidConnectSequence() throws Exception {
+        final int clientId = 4565;
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+
+        // (1) connect + success
+        mDut.attach(mockCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
+                clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(any(WifiAwareSession.class));
+
+        // (2) connect + success
+        mDut.attach(mockCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
+                clientProxyCallback.capture(), (ConfigRequest) isNull(), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId + 1);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(any(WifiAwareSession.class));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService);
+    }
+
+    /*
+     * WifiAwareDiscoverySessionCallbackProxy Tests
+     */
+
+    /**
+     * Validate the publish flow: (0) connect + success, (1) publish, (2)
+     * success creates session, (3) pass through everything, (4) update publish
+     * through session, (5) terminate locally, (6) try another command -
+     * ignored.
+     */
+    @Test
+    public void testPublishFlow() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+        final WifiAwareManager.PeerHandle peerHandle = new WifiAwareManager.PeerHandle(873);
+        final String string1 = "hey from here...";
+        final String string2 = "some other arbitrary string...";
+        final int messageId = 2123;
+        final int reason = AWARE_STATUS_ERROR;
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
+                mockPublishSession);
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+        ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<WifiAwarePublishDiscoverySession> publishSession = ArgumentCaptor
+                .forClass(WifiAwarePublishDiscoverySession.class);
+        ArgumentCaptor<WifiAwareManager.PeerHandle> peerIdCaptor = ArgumentCaptor.forClass(
+                WifiAwareManager.PeerHandle.class);
+
+        // (0) connect + success
+        mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
+        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
+                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+        WifiAwareSession session = sessionCaptor.getValue();
+
+        // (1) publish
+        session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+
+        // (2) publish session created
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+
+        // (3) ...
+        publishSession.getValue().sendMessage(peerHandle, messageId, string1.getBytes());
+        sessionProxyCallback.getValue().onMatch(peerHandle.peerId, string1.getBytes(),
+                string2.getBytes());
+        sessionProxyCallback.getValue().onMessageReceived(peerHandle.peerId, string1.getBytes());
+        sessionProxyCallback.getValue().onMessageSendFail(messageId, reason);
+        sessionProxyCallback.getValue().onMessageSendSuccess(messageId);
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mockAwareService).sendMessage(eq(clientId), eq(sessionId),
+                eq(peerHandle.peerId), eq(string1.getBytes()), eq(messageId), eq(0));
+        inOrder.verify(mockSessionCallback).onServiceDiscovered(peerIdCaptor.capture(),
+                eq(string1.getBytes()), eq(string2.getBytes()));
+        assertEquals(((WifiAwareManager.PeerHandle) peerIdCaptor.getValue()).peerId,
+                peerHandle.peerId);
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerIdCaptor.capture(),
+                eq(string1.getBytes()));
+        assertEquals(((WifiAwareManager.PeerHandle) peerIdCaptor.getValue()).peerId,
+                peerHandle.peerId);
+        inOrder.verify(mockSessionCallback).onMessageSendFailed(eq(messageId));
+        inOrder.verify(mockSessionCallback).onMessageSendSucceeded(eq(messageId));
+
+        // (4) update publish
+        publishSession.getValue().updatePublish(publishConfig);
+        sessionProxyCallback.getValue().onSessionConfigFail(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockAwareService).updatePublish(eq(clientId), eq(sessionId),
+                eq(publishConfig));
+        inOrder.verify(mockSessionCallback).onSessionConfigFailed();
+
+        // (5) terminate
+        publishSession.getValue().destroy();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockAwareService).terminateSession(clientId, sessionId);
+
+        // (6) try an update (nothing)
+        publishSession.getValue().updatePublish(publishConfig);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
+                mockPublishSession);
+    }
+
+    /**
+     * Validate race condition of session terminate and session action: (1)
+     * connect, (2) publish success + terminate, (3) update.
+     */
+    @Test
+    public void testPublishRemoteTerminate() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+        final int reason = WifiAwareDiscoverySessionCallback.TERMINATE_REASON_DONE;
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
+                mockPublishSession);
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+        ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<WifiAwarePublishDiscoverySession> publishSession = ArgumentCaptor
+                .forClass(WifiAwarePublishDiscoverySession.class);
+
+        // (1) connect successfully
+        mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
+        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
+                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+        WifiAwareSession session = sessionCaptor.getValue();
+
+        // (2) publish: successfully - then terminated
+        session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        sessionProxyCallback.getValue().onSessionTerminated(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+        inOrder.verify(mockSessionCallback).onSessionTerminated(reason);
+
+        // (3) failure when trying to update: NOP
+        publishSession.getValue().updatePublish(publishConfig);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
+                mockPublishSession);
+    }
+
+    /**
+     * Validate the subscribe flow: (0) connect + success, (1) subscribe, (2)
+     * success creates session, (3) pass through everything, (4) update
+     * subscribe through session, (5) terminate locally, (6) try another command
+     * - ignored.
+     */
+    @Test
+    public void testSubscribeFlow() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+        final WifiAwareManager.PeerHandle peerHandle = new WifiAwareManager.PeerHandle(873);
+        final String string1 = "hey from here...";
+        final String string2 = "some other arbitrary string...";
+        final int messageId = 2123;
+        final int reason = AWARE_STATUS_ERROR;
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
+                mockSubscribeSession);
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+        ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<WifiAwareSubscribeDiscoverySession> subscribeSession = ArgumentCaptor
+                .forClass(WifiAwareSubscribeDiscoverySession.class);
+        ArgumentCaptor<WifiAwareManager.PeerHandle> peerIdCaptor = ArgumentCaptor.forClass(
+                WifiAwareManager.PeerHandle.class);
+
+        // (0) connect + success
+        mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
+        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
+                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+        WifiAwareSession session = sessionCaptor.getValue();
+
+        // (1) subscribe
+        session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
+                sessionProxyCallback.capture());
+
+        // (2) subscribe session created
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSubscribeStarted(subscribeSession.capture());
+
+        // (3) ...
+        subscribeSession.getValue().sendMessage(peerHandle, messageId, string1.getBytes());
+        sessionProxyCallback.getValue().onMatch(peerHandle.peerId, string1.getBytes(),
+                string2.getBytes());
+        sessionProxyCallback.getValue().onMessageReceived(peerHandle.peerId, string1.getBytes());
+        sessionProxyCallback.getValue().onMessageSendFail(messageId, reason);
+        sessionProxyCallback.getValue().onMessageSendSuccess(messageId);
+        mMockLooper.dispatchAll();
+
+        inOrder.verify(mockAwareService).sendMessage(eq(clientId), eq(sessionId),
+                eq(peerHandle.peerId), eq(string1.getBytes()), eq(messageId), eq(0));
+        inOrder.verify(mockSessionCallback).onServiceDiscovered(peerIdCaptor.capture(),
+                eq(string1.getBytes()), eq(string2.getBytes()));
+        assertEquals(((WifiAwareManager.PeerHandle) peerIdCaptor.getValue()).peerId,
+                peerHandle.peerId);
+        inOrder.verify(mockSessionCallback).onMessageReceived(peerIdCaptor.capture(),
+                eq(string1.getBytes()));
+        assertEquals(((WifiAwareManager.PeerHandle) peerIdCaptor.getValue()).peerId,
+                peerHandle.peerId);
+        inOrder.verify(mockSessionCallback).onMessageSendFailed(eq(messageId));
+        inOrder.verify(mockSessionCallback).onMessageSendSucceeded(eq(messageId));
+
+        // (4) update subscribe
+        subscribeSession.getValue().updateSubscribe(subscribeConfig);
+        sessionProxyCallback.getValue().onSessionConfigFail(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockAwareService).updateSubscribe(eq(clientId), eq(sessionId),
+                eq(subscribeConfig));
+        inOrder.verify(mockSessionCallback).onSessionConfigFailed();
+
+        // (5) terminate
+        subscribeSession.getValue().destroy();
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockAwareService).terminateSession(clientId, sessionId);
+
+        // (6) try an update (nothing)
+        subscribeSession.getValue().updateSubscribe(subscribeConfig);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
+                mockSubscribeSession);
+    }
+
+    /**
+     * Validate race condition of session terminate and session action: (1)
+     * connect, (2) subscribe success + terminate, (3) update.
+     */
+    @Test
+    public void testSubscribeRemoteTerminate() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+        final int reason = WifiAwareDiscoverySessionCallback.TERMINATE_REASON_DONE;
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
+                mockSubscribeSession);
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+        ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<WifiAwareSubscribeDiscoverySession> subscribeSession = ArgumentCaptor
+                .forClass(WifiAwareSubscribeDiscoverySession.class);
+
+        // (1) connect successfully
+        mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
+        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
+                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+        WifiAwareSession session = sessionCaptor.getValue();
+
+        // (2) subscribe: successfully - then terminated
+        session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).subscribe(eq(clientId), eq(subscribeConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        sessionProxyCallback.getValue().onSessionTerminated(reason);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onSubscribeStarted(subscribeSession.capture());
+        inOrder.verify(mockSessionCallback).onSessionTerminated(reason);
+
+        // (3) failure when trying to update: NOP
+        subscribeSession.getValue().updateSubscribe(subscribeConfig);
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
+                mockSubscribeSession);
+    }
+
+    /*
+     * ConfigRequest Tests
+     */
+
+    @Test
+    public void testConfigRequestBuilderDefaults() {
+        ConfigRequest configRequest = new ConfigRequest.Builder().build();
+
+        collector.checkThat("mClusterHigh", ConfigRequest.CLUSTER_ID_MAX,
+                equalTo(configRequest.mClusterHigh));
+        collector.checkThat("mClusterLow", ConfigRequest.CLUSTER_ID_MIN,
+                equalTo(configRequest.mClusterLow));
+        collector.checkThat("mMasterPreference", 0,
+                equalTo(configRequest.mMasterPreference));
+        collector.checkThat("mSupport5gBand", false, equalTo(configRequest.mSupport5gBand));
+    }
+
+    @Test
+    public void testConfigRequestBuilder() {
+        final int clusterHigh = 100;
+        final int clusterLow = 5;
+        final int masterPreference = 55;
+        final boolean supportBand5g = true;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
+                .setClusterLow(clusterLow).setMasterPreference(masterPreference)
+                .setSupport5gBand(supportBand5g)
+                .build();
+
+        collector.checkThat("mClusterHigh", clusterHigh, equalTo(configRequest.mClusterHigh));
+        collector.checkThat("mClusterLow", clusterLow, equalTo(configRequest.mClusterLow));
+        collector.checkThat("mMasterPreference", masterPreference,
+                equalTo(configRequest.mMasterPreference));
+        collector.checkThat("mSupport5gBand", supportBand5g, equalTo(configRequest.mSupport5gBand));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderMasterPrefNegative() {
+        ConfigRequest.Builder builder = new ConfigRequest.Builder();
+        builder.setMasterPreference(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderMasterPrefReserved1() {
+        new ConfigRequest.Builder().setMasterPreference(1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderMasterPrefReserved255() {
+        new ConfigRequest.Builder().setMasterPreference(255);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderMasterPrefTooLarge() {
+        new ConfigRequest.Builder().setMasterPreference(256);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderClusterLowNegative() {
+        new ConfigRequest.Builder().setClusterLow(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderClusterHighNegative() {
+        new ConfigRequest.Builder().setClusterHigh(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderClusterLowAboveMax() {
+        new ConfigRequest.Builder().setClusterLow(ConfigRequest.CLUSTER_ID_MAX + 1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderClusterHighAboveMax() {
+        new ConfigRequest.Builder().setClusterHigh(ConfigRequest.CLUSTER_ID_MAX + 1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testConfigRequestBuilderClusterLowLargerThanHigh() {
+        new ConfigRequest.Builder().setClusterLow(100).setClusterHigh(5).build();
+    }
+
+    @Test
+    public void testConfigRequestParcel() {
+        final int clusterHigh = 189;
+        final int clusterLow = 25;
+        final int masterPreference = 177;
+        final boolean supportBand5g = true;
+
+        ConfigRequest configRequest = new ConfigRequest.Builder().setClusterHigh(clusterHigh)
+                .setClusterLow(clusterLow).setMasterPreference(masterPreference)
+                .setSupport5gBand(supportBand5g)
+                .build();
+
+        Parcel parcelW = Parcel.obtain();
+        configRequest.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        ConfigRequest rereadConfigRequest = ConfigRequest.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(configRequest, rereadConfigRequest);
+    }
+
+    /*
+     * SubscribeConfig Tests
+     */
+
+    @Test
+    public void testSubscribeConfigBuilderDefaults() {
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+
+        collector.checkThat("mServiceName", subscribeConfig.mServiceName, equalTo(null));
+        collector.checkThat("mSubscribeType", subscribeConfig.mSubscribeType,
+                equalTo(SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE));
+        collector.checkThat("mSubscribeCount", subscribeConfig.mSubscribeCount, equalTo(0));
+        collector.checkThat("mTtlSec", subscribeConfig.mTtlSec, equalTo(0));
+        collector.checkThat("mMatchStyle", subscribeConfig.mMatchStyle,
+                equalTo(SubscribeConfig.MATCH_STYLE_ALL));
+        collector.checkThat("mEnableTerminateNotification",
+                subscribeConfig.mEnableTerminateNotification, equalTo(true));
+    }
+
+    @Test
+    public void testSubscribeConfigBuilder() {
+        final String serviceName = "some_service_or_other";
+        final String serviceSpecificInfo = "long arbitrary string with some info";
+        final byte[] matchFilter = {
+                0, 1, 16, 1, 22 };
+        final int subscribeType = SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE;
+        final int subscribeCount = 10;
+        final int subscribeTtl = 15;
+        final int matchStyle = SubscribeConfig.MATCH_STYLE_FIRST_ONLY;
+        final boolean enableTerminateNotification = false;
+
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(serviceSpecificInfo.getBytes()).setMatchFilter(matchFilter)
+                .setSubscribeType(subscribeType)
+                .setSubscribeCount(subscribeCount).setTtlSec(subscribeTtl).setMatchStyle(matchStyle)
+                .setTerminateNotificationEnabled(enableTerminateNotification).build();
+
+        collector.checkThat("mServiceName", serviceName.getBytes(),
+                equalTo(subscribeConfig.mServiceName));
+        collector.checkThat("mServiceSpecificInfo",
+                serviceSpecificInfo.getBytes(), equalTo(subscribeConfig.mServiceSpecificInfo));
+        collector.checkThat("mMatchFilter", matchFilter, equalTo(subscribeConfig.mMatchFilter));
+        collector.checkThat("mSubscribeType", subscribeType,
+                equalTo(subscribeConfig.mSubscribeType));
+        collector.checkThat("mSubscribeCount", subscribeCount,
+                equalTo(subscribeConfig.mSubscribeCount));
+        collector.checkThat("mTtlSec", subscribeTtl, equalTo(subscribeConfig.mTtlSec));
+        collector.checkThat("mMatchStyle", matchStyle, equalTo(subscribeConfig.mMatchStyle));
+        collector.checkThat("mEnableTerminateNotification", enableTerminateNotification,
+                equalTo(subscribeConfig.mEnableTerminateNotification));
+    }
+
+    @Test
+    public void testSubscribeConfigParcel() {
+        final String serviceName = "some_service_or_other";
+        final String serviceSpecificInfo = "long arbitrary string with some info";
+        final byte[] matchFilter = {
+                0, 1, 16, 1, 22 };
+        final int subscribeType = SubscribeConfig.SUBSCRIBE_TYPE_PASSIVE;
+        final int subscribeCount = 10;
+        final int subscribeTtl = 15;
+        final int matchStyle = SubscribeConfig.MATCH_STYLE_FIRST_ONLY;
+        final boolean enableTerminateNotification = true;
+
+        SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(serviceSpecificInfo.getBytes()).setMatchFilter(matchFilter)
+                .setSubscribeType(subscribeType)
+                .setSubscribeCount(subscribeCount).setTtlSec(subscribeTtl).setMatchStyle(matchStyle)
+                .setTerminateNotificationEnabled(enableTerminateNotification).build();
+
+        Parcel parcelW = Parcel.obtain();
+        subscribeConfig.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        SubscribeConfig rereadSubscribeConfig = SubscribeConfig.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(subscribeConfig, rereadSubscribeConfig);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderBadSubscribeType() {
+        new SubscribeConfig.Builder().setSubscribeType(10);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderNegativeCount() {
+        new SubscribeConfig.Builder().setSubscribeCount(-1);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderNegativeTtl() {
+        new SubscribeConfig.Builder().setTtlSec(-100);
+    }
+
+    /**
+     * Validate that a bad match style configuration throws an exception.
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testSubscribeConfigBuilderBadMatchStyle() {
+        new SubscribeConfig.Builder().setMatchStyle(10);
+    }
+
+    /*
+     * PublishConfig Tests
+     */
+
+    @Test
+    public void testPublishConfigBuilderDefaults() {
+        PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        collector.checkThat("mServiceName", publishConfig.mServiceName, equalTo(null));
+        collector.checkThat("mPublishType", publishConfig.mPublishType,
+                equalTo(PublishConfig.PUBLISH_TYPE_UNSOLICITED));
+        collector.checkThat("mPublishCount", publishConfig.mPublishCount, equalTo(0));
+        collector.checkThat("mTtlSec", publishConfig.mTtlSec, equalTo(0));
+        collector.checkThat("mEnableTerminateNotification",
+                publishConfig.mEnableTerminateNotification, equalTo(true));
+    }
+
+    @Test
+    public void testPublishConfigBuilder() {
+        final String serviceName = "some_service_or_other";
+        final String serviceSpecificInfo = "long arbitrary string with some info";
+        final byte[] matchFilter = {
+                0, 1, 16, 1, 22 };
+        final int publishType = PublishConfig.PUBLISH_TYPE_SOLICITED;
+        final int publishCount = 10;
+        final int publishTtl = 15;
+        final boolean enableTerminateNotification = false;
+
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(serviceSpecificInfo.getBytes()).setMatchFilter(matchFilter)
+                .setPublishType(publishType)
+                .setPublishCount(publishCount).setTtlSec(publishTtl)
+                .setTerminateNotificationEnabled(enableTerminateNotification).build();
+
+        collector.checkThat("mServiceName", serviceName.getBytes(),
+                equalTo(publishConfig.mServiceName));
+        collector.checkThat("mServiceSpecificInfo",
+                serviceSpecificInfo.getBytes(), equalTo(publishConfig.mServiceSpecificInfo));
+        collector.checkThat("mMatchFilter", matchFilter, equalTo(publishConfig.mMatchFilter));
+        collector.checkThat("mPublishType", publishType, equalTo(publishConfig.mPublishType));
+        collector.checkThat("mPublishCount", publishCount, equalTo(publishConfig.mPublishCount));
+        collector.checkThat("mTtlSec", publishTtl, equalTo(publishConfig.mTtlSec));
+        collector.checkThat("mEnableTerminateNotification", enableTerminateNotification,
+                equalTo(publishConfig.mEnableTerminateNotification));
+    }
+
+    @Test
+    public void testPublishConfigParcel() {
+        final String serviceName = "some_service_or_other";
+        final String serviceSpecificInfo = "long arbitrary string with some info";
+        final byte[] matchFilter = {
+                0, 1, 16, 1, 22 };
+        final int publishType = PublishConfig.PUBLISH_TYPE_SOLICITED;
+        final int publishCount = 10;
+        final int publishTtl = 15;
+        final boolean enableTerminateNotification = false;
+
+        PublishConfig publishConfig = new PublishConfig.Builder().setServiceName(serviceName)
+                .setServiceSpecificInfo(serviceSpecificInfo.getBytes()).setMatchFilter(matchFilter)
+                .setPublishType(publishType)
+                .setPublishCount(publishCount).setTtlSec(publishTtl)
+                .setTerminateNotificationEnabled(enableTerminateNotification).build();
+
+        Parcel parcelW = Parcel.obtain();
+        publishConfig.writeToParcel(parcelW, 0);
+        byte[] bytes = parcelW.marshall();
+        parcelW.recycle();
+
+        Parcel parcelR = Parcel.obtain();
+        parcelR.unmarshall(bytes, 0, bytes.length);
+        parcelR.setDataPosition(0);
+        PublishConfig rereadPublishConfig = PublishConfig.CREATOR.createFromParcel(parcelR);
+
+        assertEquals(publishConfig, rereadPublishConfig);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishConfigBuilderBadPublishType() {
+        new PublishConfig.Builder().setPublishType(5);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishConfigBuilderNegativeCount() {
+        new PublishConfig.Builder().setPublishCount(-4);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPublishConfigBuilderNegativeTtl() {
+        new PublishConfig.Builder().setTtlSec(-10);
+    }
+
+    /*
+     * Ranging tests
+     */
+
+    /**
+     * Validate ranging + success flow: (1) connect, (2) create a (publish) session, (3) start
+     * ranging, (4) ranging success callback, (5) ranging aborted callback ignored (since
+     * listener removed).
+     */
+    @Test
+    public void testRangingCallbacks() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final int rangingId = 3482;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+        final RttManager.RttParams rttParams = new RttManager.RttParams();
+        rttParams.deviceType = RttManager.RTT_PEER_NAN;
+        rttParams.bssid = Integer.toString(1234);
+        final RttManager.RttResult rttResults = new RttManager.RttResult();
+        rttResults.distance = 10;
+
+        when(mockAwareService.startRanging(anyInt(), anyInt(),
+                any(RttManager.ParcelableRttParams.class))).thenReturn(rangingId);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
+                mockPublishSession, mockRttListener);
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+        ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<WifiAwarePublishDiscoverySession> publishSession = ArgumentCaptor
+                .forClass(WifiAwarePublishDiscoverySession.class);
+        ArgumentCaptor<RttManager.ParcelableRttParams> rttParamCaptor = ArgumentCaptor
+                .forClass(RttManager.ParcelableRttParams.class);
+        ArgumentCaptor<RttManager.RttResult[]> rttResultsCaptor = ArgumentCaptor
+                .forClass(RttManager.RttResult[].class);
+
+        // (1) connect successfully
+        mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
+        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
+                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+        WifiAwareSession session = sessionCaptor.getValue();
+
+        // (2) publish successfully
+        session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+
+        // (3) start ranging
+        publishSession.getValue().startRanging(new RttManager.RttParams[]{rttParams},
+                mockRttListener);
+        inOrder.verify(mockAwareService).startRanging(eq(clientId), eq(sessionId),
+                rttParamCaptor.capture());
+        collector.checkThat("RttParams.deviceType", rttParams.deviceType,
+                equalTo(rttParamCaptor.getValue().mParams[0].deviceType));
+        collector.checkThat("RttParams.bssid", rttParams.bssid,
+                equalTo(rttParamCaptor.getValue().mParams[0].bssid));
+
+        // (4) ranging success callback
+        clientProxyCallback.getValue().onRangingSuccess(rangingId,
+                new RttManager.ParcelableRttResults(new RttManager.RttResult[] { rttResults }));
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockRttListener).onSuccess(rttResultsCaptor.capture());
+        collector.checkThat("RttResult.distance", rttResults.distance,
+                equalTo(rttResultsCaptor.getValue()[0].distance));
+
+        // (5) ranging aborted callback (should be ignored since listener cleared on first callback)
+        clientProxyCallback.getValue().onRangingAborted(rangingId);
+        mMockLooper.dispatchAll();
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
+                mockPublishSession, mockRttListener);
+    }
+
+    /*
+     * Data-path tests
+     */
+
+    /**
+     * Validate that correct network specifier is generated for client-based data-path.
+     */
+    @Test
+    public void testNetworkSpecifierWithClient() throws Exception {
+        final int clientId = 4565;
+        final int sessionId = 123;
+        final WifiAwareManager.PeerHandle peerHandle = new WifiAwareManager.PeerHandle(123412);
+        final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR;
+        final String token = "Some arbitrary token string - can really be anything";
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+        String tokenB64 = Base64.encodeToString(token.getBytes(), Base64.DEFAULT);
+
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+        ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareDiscoverySessionCallback.class);
+        ArgumentCaptor<WifiAwarePublishDiscoverySession> publishSession = ArgumentCaptor
+                .forClass(WifiAwarePublishDiscoverySession.class);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
+                mockPublishSession, mockRttListener);
+
+        // (1) connect successfully
+        mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
+        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
+                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+        WifiAwareSession session = sessionCaptor.getValue();
+
+        // (2) publish successfully
+        session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
+        inOrder.verify(mockAwareService).publish(eq(clientId), eq(publishConfig),
+                sessionProxyCallback.capture());
+        sessionProxyCallback.getValue().onSessionStarted(sessionId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+
+        // (3) request a network specifier from the session
+        String networkSpecifier = publishSession.getValue().createNetworkSpecifier(role, peerHandle,
+                token.getBytes());
+
+        // validate format
+        JSONObject jsonObject = new JSONObject(networkSpecifier);
+        collector.checkThat("role", role,
+                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
+        collector.checkThat("client_id", clientId,
+                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
+        collector.checkThat("session_id", sessionId,
+                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_SESSION_ID)));
+        collector.checkThat("peer_id", peerHandle.peerId,
+                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_ID)));
+        collector.checkThat("token", tokenB64,
+                equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_TOKEN)));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
+                mockPublishSession, mockRttListener);
+    }
+
+    /**
+     * Validate that correct network specifier is generated for a direct data-path (i.e.
+     * specifying MAC address as opposed to a client-based oqaque specification).
+     */
+    @Test
+    public void testNetworkSpecifierDirect() throws Exception {
+        final int clientId = 134;
+        final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+        final byte[] someMac = HexEncoding.decode("000102030405".toCharArray(), false);
+        final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR;
+        final String token = "Some arbitrary token string - can really be anything";
+
+        String tokenB64 = Base64.encodeToString(token.getBytes(), Base64.DEFAULT);
+
+        ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+                WifiAwareSession.class);
+        ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+                .forClass(IWifiAwareEventCallback.class);
+
+        InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
+                mockPublishSession, mockRttListener);
+
+        // (1) connect successfully
+        mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
+        inOrder.verify(mockAwareService).connect(any(IBinder.class), anyString(),
+                clientProxyCallback.capture(), eq(configRequest), eq(false));
+        clientProxyCallback.getValue().onConnectSuccess(clientId);
+        mMockLooper.dispatchAll();
+        inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+        WifiAwareSession session = sessionCaptor.getValue();
+
+        /* (2) request a direct network specifier*/
+        String networkSpecifier = session.createNetworkSpecifier(role, someMac, token.getBytes());
+
+        /* validate format*/
+        JSONObject jsonObject = new JSONObject(networkSpecifier);
+        collector.checkThat("role", role,
+                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_ROLE)));
+        collector.checkThat("client_id", clientId,
+                equalTo(jsonObject.getInt(WifiAwareManager.NETWORK_SPECIFIER_KEY_CLIENT_ID)));
+        collector.checkThat("peer_mac", someMac, equalTo(HexEncoding.decode(
+                jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_PEER_MAC).toCharArray(),
+                false)));
+        collector.checkThat("token", tokenB64,
+                equalTo(jsonObject.getString(WifiAwareManager.NETWORK_SPECIFIER_KEY_TOKEN)));
+
+        verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
+                mockPublishSession, mockRttListener);
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index b4a3acf..2350d32 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -33,6 +33,11 @@
 @SmallTest
 public class PasspointConfigurationTest {
 
+    /**
+     * Utility function for creating a {@link android.net.wifi.hotspot2.pps.HomeSP}.
+     *
+     * @return {@link android.net.wifi.hotspot2.pps.HomeSP}
+     */
     private static HomeSP createHomeSp() {
         HomeSP homeSp = new HomeSP();
         homeSp.fqdn = "fqdn";
@@ -41,6 +46,11 @@
         return homeSp;
     }
 
+    /**
+     * Utility function for creating a {@link android.net.wifi.hotspot2.pps.Credential}.
+     *
+     * @return {@link android.net.wifi.hotspot2.pps.Credential}
+     */
     private static Credential createCredential() {
         Credential cred = new Credential();
         cred.realm = "realm";
@@ -55,6 +65,12 @@
         return cred;
     }
 
+    /**
+     * Verify parcel write and read consistency for the given configuration.
+     *
+     * @param writeConfig The configuration to verify
+     * @throws Exception
+     */
     private static void verifyParcel(PasspointConfiguration writeConfig) throws Exception {
         Parcel parcel = Parcel.obtain();
         writeConfig.writeToParcel(parcel, 0);
@@ -77,6 +93,7 @@
 
     /**
      * Verify parcel read/write for a configuration that contained both HomeSP and Credential.
+     *
      * @throws Exception
      */
     @Test
@@ -158,4 +175,30 @@
         config.credential = createCredential();
         assertTrue(config.validate());
     }
-}
\ No newline at end of file
+
+    /**
+     * Verify that copy constructor works when pass in a null source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithNullSource() throws Exception {
+        PasspointConfiguration copyConfig = new PasspointConfiguration(null);
+        PasspointConfiguration defaultConfig = new PasspointConfiguration();
+        assertTrue(copyConfig.equals(defaultConfig));
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a valid source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithValidSource() throws Exception {
+        PasspointConfiguration sourceConfig = new PasspointConfiguration();
+        sourceConfig.homeSp = createHomeSp();
+        sourceConfig.credential = createCredential();
+        PasspointConfiguration copyConfig = new PasspointConfiguration(sourceConfig);
+        assertTrue(copyConfig.equals(sourceConfig));
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index 223aa52..9c8b749 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -470,4 +470,52 @@
         cred.simCredential.eapType = EAPConstants.EAP_SIM;
         assertFalse(cred.validate());
     }
+
+    /**
+     * Verify that copy constructor works when pass in a null source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithNullSource() throws Exception {
+        Credential copyCred = new Credential(null);
+        Credential defaultCred = new Credential();
+        assertTrue(copyCred.equals(defaultCred));
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a source with user credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithSourceWithUserCred() throws Exception {
+        Credential sourceCred = createCredentialWithUserCredential();
+        Credential copyCred = new Credential(sourceCred);
+        assertTrue(copyCred.equals(sourceCred));
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a source with certificate credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithSourceWithCertCred() throws Exception {
+        Credential sourceCred = createCredentialWithCertificateCredential();
+        Credential copyCred = new Credential(sourceCred);
+        assertTrue(copyCred.equals(sourceCred));
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a source with SIM credential.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorWithSourceWithSimCred() throws Exception {
+        Credential sourceCred = createCredentialWithSimCredential();
+        Credential copyCred = new Credential(sourceCred);
+        assertTrue(copyCred.equals(sourceCred));
+    }
 }
\ No newline at end of file
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
index fff1477..c707993 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/HomeSPTest.java
@@ -118,4 +118,31 @@
         homeSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
         assertTrue(homeSp.validate());
     }
+
+    /**
+     * Verify that copy constructor works when pass in a null source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorFromNullSource() throws Exception {
+        HomeSP copySp = new HomeSP(null);
+        HomeSP defaultSp = new HomeSP();
+        assertTrue(copySp.equals(defaultSp));
+    }
+
+    /**
+     * Verify that copy constructor works when pass in a valid source.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void validateCopyConstructorFromValidSource() throws Exception {
+        HomeSP sourceSp = new HomeSP();
+        sourceSp.fqdn = "fqdn";
+        sourceSp.friendlyName = "friendlyName";
+        sourceSp.roamingConsortiumOIs = new long[] {0x55, 0x66};
+        HomeSP copySp = new HomeSP(sourceSp);
+        assertTrue(copySp.equals(sourceSp));
+    }
 }