Merge "Enforce DISALLOW_ADD_MANAGED_PROFILE"
diff --git a/Android.mk b/Android.mk
index 9d1b0cc..88be12f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -38,7 +38,9 @@
 include $(CLEAR_VARS)
 
 # FRAMEWORKS_BASE_SUBDIRS comes from build/core/pathmap.mk
-LOCAL_SRC_FILES := $(call find-other-java-files,$(FRAMEWORKS_BASE_SUBDIRS))
+LOCAL_SRC_FILES := \
+        $(call find-other-java-files,$(FRAMEWORKS_BASE_SUBDIRS)) \
+        $(call all-proto-files-under, core/proto)
 
 # EventLogTags files.
 LOCAL_SRC_FILES += \
@@ -234,6 +236,9 @@
 	core/java/android/os/IDeviceIdentifiersPolicyService.aidl \
 	core/java/android/os/IDeviceIdleController.aidl \
 	core/java/android/os/IHardwarePropertiesManager.aidl \
+	core/java/android/os/IIncidentManager.aidl \
+	core/java/android/os/IIncidentReportCompletedListener.aidl \
+	core/java/android/os/IIncidentReportStatusListener.aidl \
 	core/java/android/os/IMaintenanceActivityListener.aidl \
 	core/java/android/os/IMessenger.aidl \
 	core/java/android/os/INetworkActivityListener.aidl \
@@ -528,6 +533,10 @@
     android.hardware.thermal@1.0-java-constants         \
     android.hardware.health@1.0-java-constants          \
 
+LOCAL_PROTOC_OPTIMIZE_TYPE := stream
+LOCAL_PROTOC_FLAGS := \
+    -Iexternal/protobuf/src
+
 LOCAL_MODULE := framework
 
 LOCAL_JACK_FLAGS := --multi-dex native
@@ -1385,6 +1394,35 @@
 
 include $(BUILD_JAVA_LIBRARY)
 
+# ====  c++ proto host library  ==============================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libplatformprotos
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_PROTOC_FLAGS := \
+    --include_source_info \
+    -Iexternal/protobuf/src
+LOCAL_SRC_FILES := \
+    $(call all-proto-files-under, core/proto) \
+    $(call all-proto-files-under, libs/incident/proto)
+LOCAL_C_INCLUDES := \
+    $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto
+LOCAL_EXPORT_C_INCLUDES := \
+    $(call generated-sources-dir-for,STATIC_LIBRARIES,libplatformprotos,)/proto
+include $(BUILD_HOST_SHARED_LIBRARY)
+
+
+# ====  java proto host library  ==============================
+include $(CLEAR_VARS)
+LOCAL_MODULE := platformprotos
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_PROTOC_FLAGS := \
+    -Iexternal/protobuf/src
+LOCAL_SOURCE_FILES_ALL_GENERATED := true
+LOCAL_SRC_FILES := \
+    $(call all-proto-files-under, core/proto) \
+    $(call all-proto-files-under, libs/incident/proto)
+include $(BUILD_HOST_JAVA_LIBRARY)
+
 
 # Include subdirectory makefiles
 # ============================================================
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index c28db57..75de4a7 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,5 @@
 [Hook Scripts]
-checkstyle_hook = ../../development/tools/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
                   -fw core/java/android/animation/
                       core/java/android/hardware/usb/
                       core/java/android/print/
diff --git a/api/current.txt b/api/current.txt
index cec2b9f..395d7aa 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -11769,6 +11769,8 @@
     method public static android.graphics.Bitmap createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean);
     method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config);
     method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config);
+    method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config, boolean);
+    method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config, boolean);
     method public static android.graphics.Bitmap createBitmap(int[], int, int, int, int, android.graphics.Bitmap.Config);
     method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int[], int, int, int, int, android.graphics.Bitmap.Config);
     method public static android.graphics.Bitmap createBitmap(int[], int, int, android.graphics.Bitmap.Config);
@@ -11833,6 +11835,7 @@
     enum_constant public static final deprecated android.graphics.Bitmap.Config ARGB_4444;
     enum_constant public static final android.graphics.Bitmap.Config ARGB_8888;
     enum_constant public static final android.graphics.Bitmap.Config HARDWARE;
+    enum_constant public static final android.graphics.Bitmap.Config RGBA_F16;
     enum_constant public static final android.graphics.Bitmap.Config RGB_565;
   }
 
@@ -12192,6 +12195,7 @@
     method public android.graphics.Bitmap render();
     method public android.graphics.ColorSpace.Renderer showWhitePoint(boolean);
     method public android.graphics.ColorSpace.Renderer size(int);
+    method public android.graphics.ColorSpace.Renderer uniformChromaticityScale(boolean);
   }
 
   public static class ColorSpace.Rgb extends android.graphics.ColorSpace {
@@ -12727,6 +12731,7 @@
     field public static final deprecated int RGBA_4444 = 7; // 0x7
     field public static final deprecated int RGBA_5551 = 6; // 0x6
     field public static final int RGBA_8888 = 1; // 0x1
+    field public static final int RGBA_F16 = 22; // 0x16
     field public static final int RGBX_8888 = 2; // 0x2
     field public static final deprecated int RGB_332 = 11; // 0xb
     field public static final int RGB_565 = 4; // 0x4
@@ -61421,31 +61426,31 @@
     ctor public CopyOnWriteArrayList();
     ctor public CopyOnWriteArrayList(java.util.Collection<? extends E>);
     ctor public CopyOnWriteArrayList(E[]);
-    method public synchronized boolean add(E);
-    method public synchronized void add(int, E);
-    method public synchronized boolean addAll(java.util.Collection<? extends E>);
-    method public synchronized boolean addAll(int, java.util.Collection<? extends E>);
-    method public synchronized int addAllAbsent(java.util.Collection<? extends E>);
-    method public synchronized boolean addIfAbsent(E);
-    method public synchronized void clear();
+    method public boolean add(E);
+    method public void add(int, E);
+    method public boolean addAll(java.util.Collection<? extends E>);
+    method public boolean addAll(int, java.util.Collection<? extends E>);
+    method public int addAllAbsent(java.util.Collection<? extends E>);
+    method public boolean addIfAbsent(E);
+    method public void clear();
     method public java.lang.Object clone();
     method public boolean contains(java.lang.Object);
     method public boolean containsAll(java.util.Collection<?>);
     method public void forEach(java.util.function.Consumer<? super E>);
     method public E get(int);
-    method public int indexOf(E, int);
     method public int indexOf(java.lang.Object);
+    method public int indexOf(E, int);
     method public boolean isEmpty();
     method public java.util.Iterator<E> iterator();
-    method public int lastIndexOf(E, int);
     method public int lastIndexOf(java.lang.Object);
-    method public java.util.ListIterator<E> listIterator(int);
+    method public int lastIndexOf(E, int);
     method public java.util.ListIterator<E> listIterator();
-    method public synchronized E remove(int);
-    method public synchronized boolean remove(java.lang.Object);
-    method public synchronized boolean removeAll(java.util.Collection<?>);
-    method public synchronized boolean retainAll(java.util.Collection<?>);
-    method public synchronized E set(int, E);
+    method public java.util.ListIterator<E> listIterator(int);
+    method public E remove(int);
+    method public boolean remove(java.lang.Object);
+    method public boolean removeAll(java.util.Collection<?>);
+    method public boolean retainAll(java.util.Collection<?>);
+    method public E set(int, E);
     method public int size();
     method public java.util.List<E> subList(int, int);
     method public java.lang.Object[] toArray();
diff --git a/api/system-current.txt b/api/system-current.txt
index b0236ea..8bedf98 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -12262,6 +12262,8 @@
     method public static android.graphics.Bitmap createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean);
     method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config);
     method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config);
+    method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config, boolean);
+    method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config, boolean);
     method public static android.graphics.Bitmap createBitmap(int[], int, int, int, int, android.graphics.Bitmap.Config);
     method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int[], int, int, int, int, android.graphics.Bitmap.Config);
     method public static android.graphics.Bitmap createBitmap(int[], int, int, android.graphics.Bitmap.Config);
@@ -12326,6 +12328,7 @@
     enum_constant public static final deprecated android.graphics.Bitmap.Config ARGB_4444;
     enum_constant public static final android.graphics.Bitmap.Config ARGB_8888;
     enum_constant public static final android.graphics.Bitmap.Config HARDWARE;
+    enum_constant public static final android.graphics.Bitmap.Config RGBA_F16;
     enum_constant public static final android.graphics.Bitmap.Config RGB_565;
   }
 
@@ -12685,6 +12688,7 @@
     method public android.graphics.Bitmap render();
     method public android.graphics.ColorSpace.Renderer showWhitePoint(boolean);
     method public android.graphics.ColorSpace.Renderer size(int);
+    method public android.graphics.ColorSpace.Renderer uniformChromaticityScale(boolean);
   }
 
   public static class ColorSpace.Rgb extends android.graphics.ColorSpace {
@@ -13220,6 +13224,7 @@
     field public static final deprecated int RGBA_4444 = 7; // 0x7
     field public static final deprecated int RGBA_5551 = 6; // 0x6
     field public static final int RGBA_8888 = 1; // 0x1
+    field public static final int RGBA_F16 = 22; // 0x16
     field public static final int RGBX_8888 = 2; // 0x2
     field public static final deprecated int RGB_332 = 11; // 0xb
     field public static final int RGB_565 = 4; // 0x4
@@ -31734,6 +31739,27 @@
     method public abstract android.os.IBinder asBinder();
   }
 
+  public class IncidentManager {
+    method public void reportIncident(android.os.IncidentReportArgs);
+    method public void reportIncident(java.lang.String, byte[]);
+  }
+
+  public final class IncidentReportArgs implements android.os.Parcelable {
+    ctor public IncidentReportArgs();
+    ctor public IncidentReportArgs(android.os.Parcel);
+    method public void addHeader(byte[]);
+    method public void addSection(int);
+    method public boolean containsSection(int);
+    method public int describeContents();
+    method public boolean isAll();
+    method public static android.os.IncidentReportArgs parseSetting(java.lang.String) throws java.lang.IllegalArgumentException;
+    method public void readFromParcel(android.os.Parcel);
+    method public int sectionCount();
+    method public void setAll(boolean);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.IncidentReportArgs> CREATOR;
+  }
+
   public final class LocaleList implements android.os.Parcelable {
     ctor public LocaleList(java.util.Locale...);
     method public int describeContents();
@@ -64923,31 +64949,31 @@
     ctor public CopyOnWriteArrayList();
     ctor public CopyOnWriteArrayList(java.util.Collection<? extends E>);
     ctor public CopyOnWriteArrayList(E[]);
-    method public synchronized boolean add(E);
-    method public synchronized void add(int, E);
-    method public synchronized boolean addAll(java.util.Collection<? extends E>);
-    method public synchronized boolean addAll(int, java.util.Collection<? extends E>);
-    method public synchronized int addAllAbsent(java.util.Collection<? extends E>);
-    method public synchronized boolean addIfAbsent(E);
-    method public synchronized void clear();
+    method public boolean add(E);
+    method public void add(int, E);
+    method public boolean addAll(java.util.Collection<? extends E>);
+    method public boolean addAll(int, java.util.Collection<? extends E>);
+    method public int addAllAbsent(java.util.Collection<? extends E>);
+    method public boolean addIfAbsent(E);
+    method public void clear();
     method public java.lang.Object clone();
     method public boolean contains(java.lang.Object);
     method public boolean containsAll(java.util.Collection<?>);
     method public void forEach(java.util.function.Consumer<? super E>);
     method public E get(int);
-    method public int indexOf(E, int);
     method public int indexOf(java.lang.Object);
+    method public int indexOf(E, int);
     method public boolean isEmpty();
     method public java.util.Iterator<E> iterator();
-    method public int lastIndexOf(E, int);
     method public int lastIndexOf(java.lang.Object);
-    method public java.util.ListIterator<E> listIterator(int);
+    method public int lastIndexOf(E, int);
     method public java.util.ListIterator<E> listIterator();
-    method public synchronized E remove(int);
-    method public synchronized boolean remove(java.lang.Object);
-    method public synchronized boolean removeAll(java.util.Collection<?>);
-    method public synchronized boolean retainAll(java.util.Collection<?>);
-    method public synchronized E set(int, E);
+    method public java.util.ListIterator<E> listIterator(int);
+    method public E remove(int);
+    method public boolean remove(java.lang.Object);
+    method public boolean removeAll(java.util.Collection<?>);
+    method public boolean retainAll(java.util.Collection<?>);
+    method public E set(int, E);
     method public int size();
     method public java.util.List<E> subList(int, int);
     method public java.lang.Object[] toArray();
diff --git a/api/test-current.txt b/api/test-current.txt
index c798b5e..e43d1b5 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -11800,6 +11800,8 @@
     method public static android.graphics.Bitmap createBitmap(android.graphics.Bitmap, int, int, int, int, android.graphics.Matrix, boolean);
     method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config);
     method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config);
+    method public static android.graphics.Bitmap createBitmap(int, int, android.graphics.Bitmap.Config, boolean);
+    method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int, int, android.graphics.Bitmap.Config, boolean);
     method public static android.graphics.Bitmap createBitmap(int[], int, int, int, int, android.graphics.Bitmap.Config);
     method public static android.graphics.Bitmap createBitmap(android.util.DisplayMetrics, int[], int, int, int, int, android.graphics.Bitmap.Config);
     method public static android.graphics.Bitmap createBitmap(int[], int, int, android.graphics.Bitmap.Config);
@@ -11864,6 +11866,7 @@
     enum_constant public static final deprecated android.graphics.Bitmap.Config ARGB_4444;
     enum_constant public static final android.graphics.Bitmap.Config ARGB_8888;
     enum_constant public static final android.graphics.Bitmap.Config HARDWARE;
+    enum_constant public static final android.graphics.Bitmap.Config RGBA_F16;
     enum_constant public static final android.graphics.Bitmap.Config RGB_565;
   }
 
@@ -12223,6 +12226,7 @@
     method public android.graphics.Bitmap render();
     method public android.graphics.ColorSpace.Renderer showWhitePoint(boolean);
     method public android.graphics.ColorSpace.Renderer size(int);
+    method public android.graphics.ColorSpace.Renderer uniformChromaticityScale(boolean);
   }
 
   public static class ColorSpace.Rgb extends android.graphics.ColorSpace {
@@ -12758,6 +12762,7 @@
     field public static final deprecated int RGBA_4444 = 7; // 0x7
     field public static final deprecated int RGBA_5551 = 6; // 0x6
     field public static final int RGBA_8888 = 1; // 0x1
+    field public static final int RGBA_F16 = 22; // 0x16
     field public static final int RGBX_8888 = 2; // 0x2
     field public static final deprecated int RGB_332 = 11; // 0xb
     field public static final int RGB_565 = 4; // 0x4
@@ -29307,6 +29312,27 @@
     method public abstract android.os.IBinder asBinder();
   }
 
+  public class IncidentManager {
+    method public void reportIncident(android.os.IncidentReportArgs);
+    method public void reportIncident(java.lang.String, byte[]);
+  }
+
+  public final class IncidentReportArgs implements android.os.Parcelable {
+    ctor public IncidentReportArgs();
+    ctor public IncidentReportArgs(android.os.Parcel);
+    method public void addHeader(byte[]);
+    method public void addSection(int);
+    method public boolean containsSection(int);
+    method public int describeContents();
+    method public boolean isAll();
+    method public static android.os.IncidentReportArgs parseSetting(java.lang.String) throws java.lang.IllegalArgumentException;
+    method public void readFromParcel(android.os.Parcel);
+    method public int sectionCount();
+    method public void setAll(boolean);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.os.IncidentReportArgs> CREATOR;
+  }
+
   public final class LocaleList implements android.os.Parcelable {
     ctor public LocaleList(java.util.Locale...);
     method public int describeContents();
@@ -61710,31 +61736,31 @@
     ctor public CopyOnWriteArrayList();
     ctor public CopyOnWriteArrayList(java.util.Collection<? extends E>);
     ctor public CopyOnWriteArrayList(E[]);
-    method public synchronized boolean add(E);
-    method public synchronized void add(int, E);
-    method public synchronized boolean addAll(java.util.Collection<? extends E>);
-    method public synchronized boolean addAll(int, java.util.Collection<? extends E>);
-    method public synchronized int addAllAbsent(java.util.Collection<? extends E>);
-    method public synchronized boolean addIfAbsent(E);
-    method public synchronized void clear();
+    method public boolean add(E);
+    method public void add(int, E);
+    method public boolean addAll(java.util.Collection<? extends E>);
+    method public boolean addAll(int, java.util.Collection<? extends E>);
+    method public int addAllAbsent(java.util.Collection<? extends E>);
+    method public boolean addIfAbsent(E);
+    method public void clear();
     method public java.lang.Object clone();
     method public boolean contains(java.lang.Object);
     method public boolean containsAll(java.util.Collection<?>);
     method public void forEach(java.util.function.Consumer<? super E>);
     method public E get(int);
-    method public int indexOf(E, int);
     method public int indexOf(java.lang.Object);
+    method public int indexOf(E, int);
     method public boolean isEmpty();
     method public java.util.Iterator<E> iterator();
-    method public int lastIndexOf(E, int);
     method public int lastIndexOf(java.lang.Object);
-    method public java.util.ListIterator<E> listIterator(int);
+    method public int lastIndexOf(E, int);
     method public java.util.ListIterator<E> listIterator();
-    method public synchronized E remove(int);
-    method public synchronized boolean remove(java.lang.Object);
-    method public synchronized boolean removeAll(java.util.Collection<?>);
-    method public synchronized boolean retainAll(java.util.Collection<?>);
-    method public synchronized E set(int, E);
+    method public java.util.ListIterator<E> listIterator(int);
+    method public E remove(int);
+    method public boolean remove(java.lang.Object);
+    method public boolean removeAll(java.util.Collection<?>);
+    method public boolean retainAll(java.util.Collection<?>);
+    method public E set(int, E);
     method public int size();
     method public java.util.List<E> subList(int, int);
     method public java.lang.Object[] toArray();
diff --git a/cmds/incident/Android.mk b/cmds/incident/Android.mk
new file mode 100644
index 0000000..e1c9b93
--- /dev/null
+++ b/cmds/incident/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+        main.cpp
+
+LOCAL_MODULE := incident
+
+LOCAL_SHARED_LIBRARIES := \
+        libbase \
+        libbinder \
+        libcutils \
+        liblog \
+        libutils \
+        libincident
+
+LOCAL_CFLAGS += \
+        -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
+
+LOCAL_MODULE_CLASS := EXECUTABLES
+gen_src_dir := $(local-generated-sources-dir)
+
+gen := $(gen_src_dir)/incident_sections.cpp
+$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen
+$(gen): PRIVATE_CUSTOM_TOOL = \
+    $(HOST_OUT_EXECUTABLES)/incident-section-gen > $@
+$(gen): $(HOST_OUT_EXECUTABLES)/incident-section-gen
+	$(transform-generated-source)
+LOCAL_GENERATED_SOURCES += $(gen)
+
+gen_src_dir:=
+gen:=
+
+include $(BUILD_EXECUTABLE)
diff --git a/cmds/incident/incident_sections.h b/cmds/incident/incident_sections.h
new file mode 100644
index 0000000..1972088
--- /dev/null
+++ b/cmds/incident/incident_sections.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#ifndef INCIDENT_SECTIONS_H
+#define INCIDENT_SECTIONS_H
+
+struct IncidentSection
+{
+    int id;
+    char const* name;
+};
+
+extern IncidentSection const INCIDENT_SECTIONS[];
+extern const int INCIDENT_SECTION_COUNT;
+
+#endif // INCIDENT_SECTIONS_H
diff --git a/cmds/incident/main.cpp b/cmds/incident/main.cpp
new file mode 100644
index 0000000..91b7c22
--- /dev/null
+++ b/cmds/incident/main.cpp
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "incident"
+
+#include "incident_sections.h"
+
+#include <android/os/BnIncidentReportStatusListener.h>
+#include <android/os/IIncidentManager.h>
+#include <android/os/IncidentReportArgs.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <utils/Looper.h>
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+using namespace android;
+using namespace android::base;
+using namespace android::binder;
+using namespace android::os;
+
+// ================================================================================
+class StatusListener : public BnIncidentReportStatusListener {
+public:
+    StatusListener();
+    virtual ~StatusListener();
+
+    virtual Status onReportStarted();
+    virtual Status onReportSectionStatus(int32_t section, int32_t status);
+    virtual Status onReportServiceStatus(const String16& service, int32_t status);
+    virtual Status onReportFinished();
+    virtual Status onReportFailed();
+};
+
+StatusListener::StatusListener()
+{
+}
+
+StatusListener::~StatusListener()
+{
+}
+
+Status
+StatusListener::onReportStarted()
+{
+    return Status::ok();
+}
+
+Status
+StatusListener::onReportSectionStatus(int32_t section, int32_t status)
+{
+    fprintf(stderr, "section %d status %d\n", section, status);
+    return Status::ok();
+}
+
+Status
+StatusListener::onReportServiceStatus(const String16& service, int32_t status)
+{
+    fprintf(stderr, "service '%s' status %d\n", String8(service).string(), status);
+    return Status::ok();
+}
+
+Status
+StatusListener::onReportFinished()
+{
+    fprintf(stderr, "done\n");
+    exit(0);
+    return Status::ok();
+}
+
+Status
+StatusListener::onReportFailed()
+{
+    fprintf(stderr, "failed\n");
+    exit(1);
+    return Status::ok();
+}
+
+// ================================================================================
+static IncidentSection const*
+find_section(const char* name)
+{
+    size_t low = 0;
+    size_t high = INCIDENT_SECTION_COUNT - 1;
+
+    while (low <= high) {
+        size_t mid = (low + high) >> 1;
+        IncidentSection const* section = INCIDENT_SECTIONS + mid;
+
+        int cmp = strcmp(section->name, name);
+        if (cmp < 0) {
+            low = mid + 1;
+        } else if (cmp > 0) {
+            high = mid - 1;
+        } else {
+            return section;
+        }
+    }
+    return NULL;
+}
+
+// ================================================================================
+static void
+usage(FILE* out)
+{
+    fprintf(out, "usage: incident OPTIONS [SECTION...]\n");
+    fprintf(out, "\n");
+    fprintf(out, "Takes an incident report.\n");
+    fprintf(out, "\n");
+    fprintf(out, "OPTIONS\n");
+    fprintf(out, "  -b           (default) print the report to stdout (in proto format)\n");
+    fprintf(out, "  -d           send the report into dropbox\n");
+    fprintf(out, "\n");
+    fprintf(out, "  SECTION     the field numbers of the incident report fields to include\n");
+    fprintf(out, "\n");
+}
+
+int
+main(int argc, char** argv)
+{
+    Status status;
+    IncidentReportArgs args;
+    enum { DEST_DROPBOX, DEST_STDOUT } destination = DEST_STDOUT;
+
+    // Parse the args
+    int opt;
+    while ((opt = getopt(argc, argv, "bhd")) != -1) {
+        switch (opt) {
+            case 'b':
+                destination = DEST_STDOUT;
+                break;
+            case 'h':
+                usage(stdout);
+                return 0;
+            case 'd':
+                destination = DEST_DROPBOX;
+                break;
+            default:
+                usage(stderr);
+                return 1;
+        }
+    }
+
+    if (optind == argc) {
+        args.setAll(true);
+    } else {
+        for (int i=optind; i<argc; i++) {
+            const char* arg = argv[i];
+            char* end;
+            if (arg[0] != '\0') {
+                int section = strtol(arg, &end, 0);
+                if (*end == '\0') {
+                    args.addSection(section);
+                } else {
+                    IncidentSection const* ic = find_section(arg);
+                    if (ic == NULL) {
+                        fprintf(stderr, "Invalid section: %s\n", arg);
+                        return 1;
+                    }
+                    args.addSection(ic->id);
+                }
+            }
+        }
+    }
+
+
+
+    // Start the thread pool.
+    sp<ProcessState> ps(ProcessState::self());
+    ps->startThreadPool();
+    ps->giveThreadPoolName();
+
+    // Look up the service
+    sp<IIncidentManager> service = interface_cast<IIncidentManager>(
+            defaultServiceManager()->getService(android::String16("incident")));
+    if (service == NULL) {
+        fprintf(stderr, "Couldn't look up the incident service\n");
+        return 1;
+    }
+
+    // Construct the stream
+    int fds[2];
+    pipe(fds);
+
+    unique_fd readEnd(fds[0]);
+    unique_fd writeEnd(fds[1]);
+
+    if (destination == DEST_STDOUT) {
+        // Call into the service
+        sp<StatusListener> listener(new StatusListener());
+        status = service->reportIncidentToStream(args, listener, writeEnd);
+
+        if (!status.isOk()) {
+            fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
+        }
+
+        // Wait for the result and print out the data they send.
+        //IPCThreadState::self()->joinThreadPool();
+
+        while (true) {
+            int amt = splice(fds[0], NULL, STDOUT_FILENO, NULL, 4096, 0);
+            fprintf(stderr, "spliced %d bytes\n", amt);
+            if (amt < 0) {
+                return errno;
+            } else if (amt == 0) {
+                return 0;
+            }
+        }
+    } else {
+        status = service->reportIncident(args);
+        if (!status.isOk()) {
+            fprintf(stderr, "reportIncident returned \"%s\"\n", status.toString8().string());
+            return 1;
+        } else {
+            return 0;
+        }
+    }
+
+}
diff --git a/cmds/incidentd/Android.mk b/cmds/incidentd/Android.mk
new file mode 100644
index 0000000..bacf672
--- /dev/null
+++ b/cmds/incidentd/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := incidentd
+
+LOCAL_SRC_FILES := \
+        src/FdBuffer.cpp \
+        src/IncidentService.cpp \
+        src/Reporter.cpp \
+        src/Section.cpp \
+        src/main.cpp \
+        src/protobuf.cpp \
+        src/report_directory.cpp \
+        src/section_list.cpp
+
+LOCAL_CFLAGS += \
+        -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
+
+ifeq (debug,)
+    LOCAL_CFLAGS += \
+            -g -O0
+else
+    # optimize for size (protobuf glop can get big)
+    LOCAL_CFLAGS += \
+            -Os
+endif
+
+LOCAL_SHARED_LIBRARIES := \
+        libbase \
+        libbinder \
+        libcutils \
+        libincident \
+        liblog \
+        libselinux \
+        libservices \
+        libutils
+
+ifeq (BUILD_WITH_INCIDENTD_RC,true)
+LOCAL_INIT_RC := incidentd.rc
+endif
+
+include $(BUILD_EXECUTABLE)
diff --git a/cmds/incidentd/incidentd.rc b/cmds/incidentd/incidentd.rc
new file mode 100644
index 0000000..d11e3cf
--- /dev/null
+++ b/cmds/incidentd/incidentd.rc
@@ -0,0 +1,16 @@
+# 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.
+
+#service incidentd /system/bin/incidentd
+#    class main
diff --git a/cmds/incidentd/src/FdBuffer.cpp b/cmds/incidentd/src/FdBuffer.cpp
new file mode 100644
index 0000000..527d7ee
--- /dev/null
+++ b/cmds/incidentd/src/FdBuffer.cpp
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "incidentd"
+
+#include "FdBuffer.h"
+
+#include <cutils/log.h>
+#include <utils/SystemClock.h>
+
+#include <fcntl.h>
+#include <poll.h>
+#include <unistd.h>
+
+const ssize_t BUFFER_SIZE = 16 * 1024;
+const ssize_t MAX_BUFFER_COUNT = 256; // 4 MB max
+
+
+FdBuffer::FdBuffer()
+    :mBuffers(),
+     mStartTime(-1),
+     mFinishTime(-1),
+     mCurrentWritten(-1),
+     mTimedOut(false),
+     mTruncated(false)
+{
+}
+
+FdBuffer::~FdBuffer()
+{
+    const int N = mBuffers.size();
+    for (int i=0; i<N; i++) {
+        uint8_t* buf = mBuffers[i];
+        free(buf);
+    }
+}
+
+status_t
+FdBuffer::read(int fd, int64_t timeout)
+{
+    struct pollfd pfds = {
+        .fd = fd,
+        .events = POLLIN
+    };
+    mStartTime = uptimeMillis();
+
+    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
+
+    uint8_t* buf = NULL;
+    while (true) {
+        if (mCurrentWritten >= BUFFER_SIZE || mCurrentWritten < 0) {
+            if (mBuffers.size() == MAX_BUFFER_COUNT) {
+                mTruncated = true;
+                break;
+            }
+            buf = (uint8_t*)malloc(BUFFER_SIZE);
+            if (buf == NULL) {
+                return NO_MEMORY;
+            }
+            mBuffers.push_back(buf);
+            mCurrentWritten = 0;
+        }
+
+        int64_t remainingTime = (mStartTime + timeout) - uptimeMillis();
+        if (remainingTime <= 0) {
+            mTimedOut = true;
+            break;
+        }
+
+        int count = poll(&pfds, 1, remainingTime);
+        if (count == 0) {
+            mTimedOut = true;
+            break;
+        } else if (count < 0) {
+            return -errno;
+        } else {
+            if ((pfds.revents & POLLERR) != 0) {
+                return errno != 0 ? -errno : UNKNOWN_ERROR;
+            } else {
+                ssize_t amt = ::read(fd, buf + mCurrentWritten, BUFFER_SIZE - mCurrentWritten);
+                if (amt < 0) {
+                    if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                        continue;
+                    } else {
+                        return -errno;
+                    }
+                } else if (amt == 0) {
+                    break;
+                }
+                mCurrentWritten += amt;
+            }
+        }
+    }
+
+    mFinishTime = uptimeMillis();
+    return NO_ERROR;
+}
+
+size_t
+FdBuffer::size()
+{
+    return ((mBuffers.size() - 1) * BUFFER_SIZE) + mCurrentWritten;
+}
+
+status_t
+FdBuffer::write(ReportRequestSet* reporter)
+{
+    const int N = mBuffers.size() - 1;
+    for (int i=0; i<N; i++) {
+        reporter->write(mBuffers[i], BUFFER_SIZE);
+    }
+    reporter->write(mBuffers[N], mCurrentWritten);
+    return NO_ERROR;
+}
+
+
diff --git a/cmds/incidentd/src/FdBuffer.h b/cmds/incidentd/src/FdBuffer.h
new file mode 100644
index 0000000..e12374f
--- /dev/null
+++ b/cmds/incidentd/src/FdBuffer.h
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+#ifndef FD_BUFFER_H
+#define FD_BUFFER_H
+
+#include "Reporter.h"
+
+#include <utils/Errors.h>
+
+#include <set>
+#include <vector>
+
+using namespace android;
+using namespace std;
+
+/**
+ * Reads a file into a buffer, and then writes that data to an FdSet.
+ */
+class FdBuffer
+{
+public:
+    FdBuffer();
+    ~FdBuffer();
+
+    /**
+     * Read the data until the timeout is hit or we hit eof.
+     * Returns NO_ERROR if there were no errors or if we timed out.
+     * Will mark the file O_NONBLOCK.
+     */
+    status_t read(int fd, int64_t timeoutMs);
+
+    /**
+     * Whether we timed out.
+     */
+    bool timedOut() { return mTimedOut; }
+
+    /**
+     * If more than 4 MB is read, we truncate the data and return success.
+     * Downstream tools must handle truncated incident reports as best as possible
+     * anyway because they could be cut off for a lot of reasons and it's best
+     * to get as much useful information out of the system as possible. If this
+     * happens, truncated() will return true so it can be marked. If the data is
+     * exactly 4 MB, truncated is still set. Sorry.
+     */
+    bool truncated() { return mTruncated; }
+
+    /**
+     * How much data was read.
+     */
+    size_t size();
+
+    /**
+     * Write the data that we recorded to the fd given.
+     */
+    status_t write(ReportRequestSet* requests);
+
+    /**
+     * How long the read took in milliseconds.
+     */
+    int64_t durationMs() { return mFinishTime - mStartTime; }
+
+private:
+    vector<uint8_t*> mBuffers;
+    int64_t mStartTime;
+    int64_t mFinishTime;
+    ssize_t mCurrentWritten;
+    bool mTimedOut;
+    bool mTruncated;
+};
+
+
+#endif // FD_BUFFER_H
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
new file mode 100644
index 0000000..7c6789e
--- /dev/null
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -0,0 +1,244 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "incidentd"
+
+#include "IncidentService.h"
+
+#include "Reporter.h"
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <cutils/log.h>
+#include <private/android_filesystem_config.h>
+#include <utils/Looper.h>
+
+#include <unistd.h>
+
+using namespace android;
+
+enum {
+    WHAT_RUN_REPORT = 1,
+    WHAT_SEND_BACKLOG_TO_DROPBOX = 2
+};
+
+//#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL * 60 * 5)
+#define DEFAULT_BACKLOG_DELAY_NS (1000000000LL)
+
+// ================================================================================
+String16 const DUMP_PERMISSION("android.permission.DUMP");
+String16 const USAGE_STATS_PERMISSION("android.permission.PACKAGE_USAGE_STATS");
+
+static Status
+checkIncidentPermissions()
+{
+    if (!checkCallingPermission(DUMP_PERMISSION)) {
+        ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP",
+                IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid());
+        return Status::fromExceptionCode(Status::EX_SECURITY,
+                "Calling process does not have permission: android.permission.DUMP");
+    }
+    if (!checkCallingPermission(USAGE_STATS_PERMISSION)) {
+        ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS",
+                IPCThreadState::self()->getCallingPid(), IPCThreadState::self()->getCallingUid());
+        return Status::fromExceptionCode(Status::EX_SECURITY,
+                "Calling process does not have permission: android.permission.USAGE_STATS");
+    }
+    return Status::ok();
+}
+
+
+// ================================================================================
+ReportRequestQueue::ReportRequestQueue()
+{
+}
+
+ReportRequestQueue::~ReportRequestQueue()
+{
+}
+
+void
+ReportRequestQueue::addRequest(const sp<ReportRequest>& request) 
+{
+    unique_lock<mutex> lock(mLock);
+    mQueue.push_back(request);
+}
+
+sp<ReportRequest>
+ReportRequestQueue::getNextRequest()
+{
+    unique_lock<mutex> lock(mLock);
+    if (mQueue.empty()) {
+        return NULL;
+    } else {
+        sp<ReportRequest> front(mQueue.front());
+        mQueue.pop_front();
+        return front;
+    }
+}
+
+
+// ================================================================================
+ReportHandler::ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue)
+    :mBacklogDelay(DEFAULT_BACKLOG_DELAY_NS),
+     mHandlerLooper(handlerLooper),
+     mQueue(queue)
+{
+}
+
+ReportHandler::~ReportHandler()
+{
+}
+
+void
+ReportHandler::handleMessage(const Message& message)
+{
+    switch (message.what) {
+        case WHAT_RUN_REPORT:
+            run_report();
+            break;
+        case WHAT_SEND_BACKLOG_TO_DROPBOX:
+            send_backlog_to_dropbox();
+            break;
+    }
+}
+
+void
+ReportHandler::scheduleRunReport(const sp<ReportRequest>& request)
+{
+    mQueue->addRequest(request);
+    mHandlerLooper->removeMessages(this, WHAT_RUN_REPORT);
+    mHandlerLooper->sendMessage(this, Message(WHAT_RUN_REPORT));
+}
+
+void
+ReportHandler::scheduleSendBacklogToDropbox()
+{
+    unique_lock<mutex> lock(mLock);
+    mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS;
+    schedule_send_backlog_to_dropbox_locked();
+}
+
+void
+ReportHandler::schedule_send_backlog_to_dropbox_locked()
+{
+    mHandlerLooper->removeMessages(this, WHAT_SEND_BACKLOG_TO_DROPBOX);
+    mHandlerLooper->sendMessageDelayed(mBacklogDelay, this,
+            Message(WHAT_SEND_BACKLOG_TO_DROPBOX));
+}
+
+void
+ReportHandler::run_report()
+{
+    sp<Reporter> reporter = new Reporter();
+
+    // Merge all of the requests into one that has all of the
+    // requested fields.
+    while (true) {
+        sp<ReportRequest> request = mQueue->getNextRequest();
+        if (request == NULL) {
+            break;
+        }
+        reporter->batch.add(request);
+        reporter->args.merge(request->args);
+    }
+
+    // Take the report, which might take a while. More requests might queue
+    // up while we're doing this, and we'll handle them in their next batch.
+    // TODO: We should further rate-limit the reports to no more than N per time-period.
+    Reporter::run_report_status_t reportStatus = reporter->runReport();
+    if (reportStatus == Reporter::REPORT_NEEDS_DROPBOX) {
+        unique_lock<mutex> lock(mLock);
+        schedule_send_backlog_to_dropbox_locked();
+    }
+}
+
+void
+ReportHandler::send_backlog_to_dropbox()
+{
+    if (Reporter::upload_backlog() == Reporter::REPORT_NEEDS_DROPBOX) {
+        // There was a failure. Exponential backoff.
+        unique_lock<mutex> lock(mLock);
+        mBacklogDelay *= 2;
+        ALOGI("Error sending to dropbox. Trying again in %lld minutes",
+                (mBacklogDelay / (1000000000LL * 60)));
+        schedule_send_backlog_to_dropbox_locked();
+    } else {
+        mBacklogDelay = DEFAULT_BACKLOG_DELAY_NS;
+    }
+}
+
+// ================================================================================
+IncidentService::IncidentService(const sp<Looper>& handlerLooper)
+    :mQueue(new ReportRequestQueue())
+{
+    mHandler = new ReportHandler(handlerLooper, mQueue);
+}
+
+IncidentService::~IncidentService()
+{
+}
+
+Status
+IncidentService::reportIncident(const IncidentReportArgs& args)
+{
+    ALOGI("reportIncident");
+
+    Status status = checkIncidentPermissions();
+    if (!status.isOk()) {
+        return status;
+    }
+
+    mHandler->scheduleRunReport(new ReportRequest(args, NULL, -1));
+
+    return Status::ok();
+}
+
+Status
+IncidentService::reportIncidentToStream(const IncidentReportArgs& args,
+            const sp<IIncidentReportStatusListener>& listener, const unique_fd& stream)
+{
+    ALOGI("reportIncidentToStream");
+
+    Status status = checkIncidentPermissions();
+    if (!status.isOk()) {
+        return status;
+    }
+
+    int fd = dup(stream.get());
+    if (fd < 0) {
+        return Status::fromStatusT(-errno);
+    }
+
+    mHandler->scheduleRunReport(new ReportRequest(args, listener, fd));
+
+    return Status::ok();
+}
+
+Status
+IncidentService::systemRunning()
+{
+    if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
+        return Status::fromExceptionCode(Status::EX_SECURITY,
+                "Only system uid can call systemRunning");
+    }
+    
+    // When system_server is up and running, schedule the dropbox task to run.
+    mHandler->scheduleSendBacklogToDropbox();
+
+    return Status::ok();
+}
+
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
new file mode 100644
index 0000000..d6f33df
--- /dev/null
+++ b/cmds/incidentd/src/IncidentService.h
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+#ifndef INCIDENT_SERVICE_H
+#define INCIDENT_SERVICE_H
+
+#include "Reporter.h"
+
+#include <android/os/BnIncidentManager.h>
+#include <utils/Looper.h>
+
+#include <deque>
+#include <mutex>
+
+using namespace android;
+using namespace android::base;
+using namespace android::binder;
+using namespace android::os;
+using namespace std;
+
+// ================================================================================
+class ReportRequestQueue : public virtual RefBase
+{
+public:
+    ReportRequestQueue();
+    virtual ~ReportRequestQueue();
+
+    void addRequest(const sp<ReportRequest>& request);
+    sp<ReportRequest> getNextRequest();
+
+private:
+    mutex mLock;
+    deque<sp<ReportRequest> > mQueue;
+};
+
+
+// ================================================================================
+class ReportHandler : public MessageHandler
+{
+public:
+    ReportHandler(const sp<Looper>& handlerLooper, const sp<ReportRequestQueue>& queue);
+    virtual ~ReportHandler();
+
+    virtual void handleMessage(const Message& message);
+
+    /**
+     * Adds a ReportRequest to the queue.
+     */
+    void scheduleRunReport(const sp<ReportRequest>& request);
+
+    /**
+     * Resets mBacklogDelay to the default and schedules sending
+     * the messages to dropbox.
+     */
+    void scheduleSendBacklogToDropbox();
+
+private:
+    mutex mLock;
+    nsecs_t mBacklogDelay;
+    sp<Looper> mHandlerLooper;
+    sp<ReportRequestQueue> mQueue;
+
+    /**
+     * Runs all of the reports that have been queued.
+     */
+    void run_report();
+
+    /**
+     * Schedules a dropbox task mBacklogDelay nanoseconds from now.
+     */
+    void schedule_send_backlog_to_dropbox_locked();
+
+    /**
+     * Sends the backlog to the dropbox service.
+     */
+    void send_backlog_to_dropbox();
+};
+
+
+// ================================================================================
+class IncidentService : public BnIncidentManager {
+public:
+    IncidentService(const sp<Looper>& handlerLooper);
+    virtual ~IncidentService();
+
+    virtual Status reportIncident(const IncidentReportArgs& args);
+
+    virtual Status reportIncidentToStream(const IncidentReportArgs& args,
+            const sp<IIncidentReportStatusListener>& listener, const unique_fd& stream);
+
+    virtual Status systemRunning();
+
+private:
+    sp<ReportRequestQueue> mQueue;
+    sp<ReportHandler> mHandler;
+};
+
+
+#endif // INCIDENT_SERVICE_H
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
new file mode 100644
index 0000000..1ecb291
--- /dev/null
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -0,0 +1,352 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "incidentd"
+
+#include "Reporter.h"
+#include "protobuf.h"
+
+#include "report_directory.h"
+#include "section_list.h"
+
+#include <private/android_filesystem_config.h>
+#include <android/os/DropBoxManager.h>
+#include <utils/SystemClock.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <errno.h>
+
+/**
+ * The directory where the incident reports are stored.
+ */
+static const String8 INCIDENT_DIRECTORY("/data/incidents");
+
+static status_t
+write_all(int fd, uint8_t const* buf, size_t size)
+{
+    while (size > 0) {
+        ssize_t amt = ::write(fd, buf, size);
+        if (amt < 0) {
+            return -errno;
+        }
+        size -= amt;
+        buf += amt;
+    }
+    return NO_ERROR;
+}
+
+// ================================================================================
+ReportRequest::ReportRequest(const IncidentReportArgs& a,
+            const sp<IIncidentReportStatusListener> &l, int f)
+    :args(a),
+     listener(l),
+     fd(f),
+     err(NO_ERROR)
+{
+}
+
+ReportRequest::~ReportRequest()
+{
+}
+
+// ================================================================================
+ReportRequestSet::ReportRequestSet()
+    :mRequests(),
+     mWritableCount(0),
+     mMainFd(-1)
+{
+}
+
+ReportRequestSet::~ReportRequestSet()
+{
+}
+
+void
+ReportRequestSet::add(const sp<ReportRequest>& request)
+{
+    mRequests.push_back(request);
+    mWritableCount++;
+}
+
+void
+ReportRequestSet::setMainFd(int fd)
+{
+    mMainFd = fd;
+    mWritableCount++;
+}
+
+status_t
+ReportRequestSet::write(uint8_t const* buf, size_t size)
+{
+    status_t err = EBADF;
+
+    // The streaming ones
+    int const N = mRequests.size();
+    for (int i=N-1; i>=0; i--) {
+        sp<ReportRequest> request = mRequests[i];
+        if (request->fd >= 0 && request->err == NO_ERROR) {
+            err = write_all(request->fd, buf, size);
+            if (err != NO_ERROR) {
+                request->err = err;
+                mWritableCount--;
+            }
+        }
+    }
+
+    // The dropbox file
+    if (mMainFd >= 0) {
+        err = write_all(mMainFd, buf, size);
+        if (err != NO_ERROR) {
+            mMainFd = -1;
+            mWritableCount--;
+        }
+    }
+
+    // Return an error only when there are no FDs to write.
+    return mWritableCount > 0 ? NO_ERROR : err;
+}
+
+
+// ================================================================================
+Reporter::Reporter()
+    :args(),
+     batch()
+{
+    char buf[100];
+
+    // TODO: Make the max size smaller for user builds.
+    mMaxSize = 100 * 1024 * 1024;
+    mMaxCount = 100;
+
+    // There can't be two at the same time because it's on one thread.
+    mStartTime = time(NULL);
+    strftime(buf, sizeof(buf), "/incident-%Y%m%d-%H%M%S", localtime(&mStartTime));
+    mFilename = INCIDENT_DIRECTORY + buf;
+}
+
+Reporter::~Reporter()
+{
+}
+
+Reporter::run_report_status_t
+Reporter::runReport()
+{
+
+    status_t err = NO_ERROR;
+    bool needMainFd = false;
+    int mainFd = -1;
+
+    // See if we need the main file
+    for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
+        if ((*it)->fd < 0 && mainFd < 0) {
+            needMainFd = true;
+            break;
+        }
+    }
+    if (needMainFd) {
+        // Create the directory
+        err = create_directory(INCIDENT_DIRECTORY);
+        if (err != NO_ERROR) {
+            goto done;
+        }
+
+        // If there are too many files in the directory (for whatever reason),
+        // delete the oldest ones until it's under the limit. Doing this first
+        // does mean that we can go over, so the max size is not a hard limit.
+        clean_directory(INCIDENT_DIRECTORY, mMaxSize, mMaxCount);
+
+        // Open the file.
+        err = create_file(&mainFd);
+        if (err != NO_ERROR) {
+            goto done;
+        }
+
+        // Add to the set
+        batch.setMainFd(mainFd);
+    }
+
+    // Tell everyone that we're starting.
+    for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
+        if ((*it)->listener != NULL) {
+            (*it)->listener->onReportStarted();
+        }
+    }
+
+    // Write the incident headers
+    for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
+        const sp<ReportRequest> request = (*it);
+        const vector<vector<int8_t>>& headers = request->args.headers();
+
+        for (vector<vector<int8_t>>::const_iterator buf=headers.begin(); buf!=headers.end();
+                buf++) {
+            int fd = request->fd >= 0 ? request->fd : mainFd;
+
+            uint8_t buffer[20];
+            uint8_t* p = write_length_delimited_tag_header(buffer, FIELD_ID_INCIDENT_HEADER,
+                    buf->size());
+            write_all(fd, buffer, p-buffer);
+
+            write_all(fd, (uint8_t const*)buf->data(), buf->size());
+            // If there was an error now, there will be an error later and we will remove
+            // it from the list then.
+        }
+    }
+
+    // For each of the report fields, see if we need it, and if so, execute the command
+    // and report to those that care that we're doing it.
+    for (const Section** section=SECTION_LIST; *section; section++) {
+        const int id = (*section)->id;
+        ALOGD("Taking incident report section %d '%s'", id, (*section)->name.string());
+
+        if (this->args.containsSection(id)) {
+            // Notify listener of starting
+            for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
+                if ((*it)->listener != NULL && (*it)->args.containsSection(id)) {
+                    (*it)->listener->onReportSectionStatus(id,
+                            IIncidentReportStatusListener::STATUS_STARTING);
+                }
+            }
+
+            // Execute - go get the data and write it into the file descriptors.
+            err = (*section)->Execute(&batch);
+            if (err != NO_ERROR) {
+                ALOGW("Incident section %s (%d) failed. Stopping report.",
+                        (*section)->name.string(), id);
+                goto done;
+            }
+
+            // Notify listener of starting
+            for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
+                if ((*it)->listener != NULL && (*it)->args.containsSection(id)) {
+                    (*it)->listener->onReportSectionStatus(id,
+                            IIncidentReportStatusListener::STATUS_FINISHED);
+                }
+            }
+        }
+    }
+
+done:
+    // Close the file.
+    if (mainFd >= 0) {
+        close(mainFd);
+    }
+
+    // Tell everyone that we're done.
+    for (ReportRequestSet::iterator it=batch.begin(); it!=batch.end(); it++) {
+        if ((*it)->listener != NULL) {
+            if (err == NO_ERROR) {
+                (*it)->listener->onReportFinished();
+            } else {
+                (*it)->listener->onReportFailed();
+            }
+        }
+    }
+
+    // Put the report into dropbox.
+    if (needMainFd && err == NO_ERROR) {
+        sp<DropBoxManager> dropbox = new DropBoxManager();
+        Status status = dropbox->addFile(String16("incident"), mFilename, 0);
+        ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string());
+        if (!status.isOk()) {
+            return REPORT_NEEDS_DROPBOX;
+        }
+
+        // If the status was ok, delete the file. If not, leave it around until the next
+        // boot or the next checkin. If the directory gets too big older files will
+        // be rotated out.
+        unlink(mFilename.c_str());
+    }
+
+    return REPORT_FINISHED;
+}
+
+/**
+ * Create our output file and set the access permissions to -rw-rw----
+ */
+status_t
+Reporter::create_file(int* fd)
+{
+    const char* filename = mFilename.c_str();
+
+    *fd = open(filename, O_CREAT | O_TRUNC | O_RDWR, 0660);
+    if (*fd < 0) {
+        ALOGE("Couldn't open incident file: %s (%s)", filename, strerror(errno));
+        return -errno;
+    }
+
+    // Override umask. Not super critical. If it fails go on with life.
+    chmod(filename, 0660);
+
+    if (chown(filename, AID_SYSTEM, AID_SYSTEM)) {
+        ALOGE("Unable to change ownership of incident file %s: %s\n", filename, strerror(errno));
+        status_t err = -errno;
+        unlink(mFilename.c_str());
+        return err;
+    }
+
+    return NO_ERROR;
+}
+
+// ================================================================================
+Reporter::run_report_status_t
+Reporter::upload_backlog()
+{
+    DIR* dir;
+    struct dirent* entry;
+    struct stat st;
+
+    if ((dir = opendir(INCIDENT_DIRECTORY.string())) == NULL) {
+        ALOGE("Couldn't open incident directory: %s", INCIDENT_DIRECTORY.string());
+        return REPORT_NEEDS_DROPBOX;
+    }
+
+    String8 dirbase(INCIDENT_DIRECTORY + "/");
+    sp<DropBoxManager> dropbox = new DropBoxManager();
+
+    // Enumerate, count and add up size
+    while ((entry = readdir(dir)) != NULL) {
+        if (entry->d_name[0] == '.') {
+            continue;
+        }
+        String8 filename = dirbase + entry->d_name;
+        if (stat(filename.string(), &st) != 0) {
+            ALOGE("Unable to stat file %s", filename.string());
+            continue;
+        }
+        if (!S_ISREG(st.st_mode)) {
+            continue;
+        }
+
+        Status status = dropbox->addFile(String16("incident"), filename.string(), 0);
+        ALOGD("Incident report done. dropbox status=%s\n", status.toString8().string());
+        if (!status.isOk()) {
+            return REPORT_NEEDS_DROPBOX;
+        }
+
+        // If the status was ok, delete the file. If not, leave it around until the next
+        // boot or the next checkin. If the directory gets too big older files will
+        // be rotated out.
+        unlink(filename.string());
+    }
+
+    closedir(dir);
+
+    return REPORT_FINISHED;
+}
+
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
new file mode 100644
index 0000000..5b86561
--- /dev/null
+++ b/cmds/incidentd/src/Reporter.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef REPORTER_H
+#define REPORTER_H
+
+#include <android/os/IIncidentReportStatusListener.h>
+#include <android/os/IncidentReportArgs.h>
+
+#include <string>
+#include <vector>
+
+#include <time.h>
+
+using namespace android;
+using namespace android::os;
+using namespace std;
+
+// ================================================================================
+struct ReportRequest : public virtual RefBase
+{
+    IncidentReportArgs args;
+    sp<IIncidentReportStatusListener> listener;
+    int fd;
+    status_t err;
+
+    ReportRequest(const IncidentReportArgs& args,
+            const sp<IIncidentReportStatusListener> &listener, int fd);
+    virtual ~ReportRequest();
+};
+
+// ================================================================================
+class ReportRequestSet
+{
+public:
+    ReportRequestSet();
+    ~ReportRequestSet();
+
+    void add(const sp<ReportRequest>& request);
+    void setMainFd(int fd);
+
+    // Write to all of the fds for the requests. If a write fails, it stops
+    // writing to that fd and returns NO_ERROR. When we are out of fds to write
+    // to it returns an error.
+    status_t write(uint8_t const* buf, size_t size);
+
+    typedef vector<sp<ReportRequest>>::iterator iterator;
+
+    iterator begin() { return mRequests.begin(); }
+    iterator end() { return mRequests.end(); }
+
+private:
+    vector<sp<ReportRequest>> mRequests;
+    int mWritableCount;
+    int mMainFd;
+};
+
+// ================================================================================
+class Reporter : public virtual RefBase
+{
+public:
+    enum run_report_status_t {
+        REPORT_FINISHED = 0,
+        REPORT_NEEDS_DROPBOX = 1
+    };
+
+    IncidentReportArgs args;
+    ReportRequestSet batch;
+
+    Reporter();
+    virtual ~Reporter();
+
+    // Run the report as described in the batch and args parameters.
+    run_report_status_t runReport();
+
+    static run_report_status_t upload_backlog();
+
+private:
+    string mFilename;
+    off_t mMaxSize;
+    size_t mMaxCount;
+    time_t mStartTime;
+
+    status_t create_file(int* fd);
+};
+
+
+#endif // REPORTER_H
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
new file mode 100644
index 0000000..fac299e
--- /dev/null
+++ b/cmds/incidentd/src/Section.cpp
@@ -0,0 +1,292 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "incidentd"
+
+#include "Section.h"
+#include "protobuf.h"
+
+#include <binder/IServiceManager.h>
+#include <mutex>
+
+using namespace std;
+
+const int64_t REMOTE_CALL_TIMEOUT_MS = 10 * 1000; // 10 seconds
+
+// ================================================================================
+Section::Section(int i)
+    :id(i)
+{
+}
+
+Section::~Section()
+{
+}
+
+status_t
+Section::WriteHeader(ReportRequestSet* requests, size_t size) const
+{
+    ssize_t amt;
+    uint8_t buf[20];
+    uint8_t* p = write_length_delimited_tag_header(buf, this->id, size);
+    return requests->write(buf, p-buf);
+}
+
+// ================================================================================
+struct WorkerThreadData : public virtual RefBase
+{
+    const WorkerThreadSection* section;
+    int fds[2];
+
+    // Lock protects these fields
+    mutex lock;
+    bool workerDone;
+    status_t workerError;
+
+    WorkerThreadData(const WorkerThreadSection* section);
+    virtual ~WorkerThreadData();
+
+    int readFd() { return fds[0]; }
+    int writeFd() { return fds[1]; }
+};
+
+WorkerThreadData::WorkerThreadData(const WorkerThreadSection* sec)
+    :section(sec),
+     workerDone(false),
+     workerError(NO_ERROR)
+{
+    fds[0] = -1;
+    fds[1] = -1;
+}
+
+WorkerThreadData::~WorkerThreadData()
+{
+}
+
+// ================================================================================
+WorkerThreadSection::WorkerThreadSection(int id)
+    :Section(id)
+{
+}
+
+WorkerThreadSection::~WorkerThreadSection()
+{
+}
+
+static void*
+worker_thread_func(void* cookie)
+{
+    WorkerThreadData* data = (WorkerThreadData*)cookie;
+    status_t err = data->section->BlockingCall(data->writeFd());
+
+    {
+        unique_lock<mutex> lock(data->lock);
+        data->workerDone = true;
+        data->workerError = err;
+    }
+
+    close(data->writeFd());
+    data->decStrong(data->section);
+    // data might be gone now. don't use it after this point in this thread.
+    return NULL;
+}
+
+status_t
+WorkerThreadSection::Execute(ReportRequestSet* requests) const
+{
+    status_t err = NO_ERROR;
+    pthread_t thread;
+    pthread_attr_t attr;
+    bool timedOut = false;
+    FdBuffer buffer;
+
+    // Data shared between this thread and the worker thread.
+    sp<WorkerThreadData> data = new WorkerThreadData(this);
+
+    // Create the pipe
+    err = pipe(data->fds);
+    if (err != 0) {
+        return -errno;
+    }
+
+    // The worker thread needs a reference and we can't let the count go to zero
+    // if that thread is slow to start.
+    data->incStrong(this);
+
+    // Create the thread
+    err = pthread_attr_init(&attr);
+    if (err != 0) {
+        return -err;
+    }
+    // TODO: Do we need to tweak thread priority?
+    err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+    if (err != 0) {
+        pthread_attr_destroy(&attr);
+        return -err;
+    }
+    err = pthread_create(&thread, &attr, worker_thread_func, (void*)data.get());
+    if (err != 0) {
+        pthread_attr_destroy(&attr);
+        return -err;
+    }
+    pthread_attr_destroy(&attr);
+
+    // Loop reading until either the timeout or the worker side is done (i.e. eof).
+    err = buffer.read(data->readFd(), REMOTE_CALL_TIMEOUT_MS);
+    if (err != NO_ERROR) {
+        // TODO: Log this error into the incident report.
+        ALOGW("WorkerThreadSection '%s' reader failed with error '%s'", this->name.string(),
+                strerror(-err));
+    }
+
+    // Done with the read fd. The worker thread closes the write one so
+    // we never race and get here first.
+    close(data->readFd());
+
+    // If the worker side is finished, then return its error (which may overwrite
+    // our possible error -- but it's more interesting anyway).  If not, then we timed out.
+    {
+        unique_lock<mutex> lock(data->lock);
+        if (!data->workerDone) {
+            // We timed out
+            timedOut = true;
+        } else {
+            if (data->workerError != NO_ERROR) {
+                err = data->workerError;
+                // TODO: Log this error into the incident report.
+                ALOGW("WorkerThreadSection '%s' worker failed with error '%s'", this->name.string(),
+                        strerror(-err));
+            }
+        }
+    }
+
+    if (timedOut || buffer.timedOut()) {
+        ALOGW("WorkerThreadSection '%s' timed out", this->name.string());
+        return NO_ERROR;
+    }
+
+    if (buffer.truncated()) {
+        // TODO: Log this into the incident report.
+    }
+
+    // TODO: There was an error with the command or buffering. Report that.  For now
+    // just exit with a log messasge.
+    if (err != NO_ERROR) {
+        ALOGW("WorkerThreadSection '%s' failed with error '%s'", this->name.string(),
+                strerror(-err));
+        return NO_ERROR;
+    }
+
+    // Write the data that was collected
+    ALOGD("section '%s' wrote %zd bytes in %d ms", name.string(), buffer.size(),
+            (int)buffer.durationMs());
+    WriteHeader(requests, buffer.size());
+    err = buffer.write(requests);
+    if (err != NO_ERROR) {
+        ALOGW("WorkerThreadSection '%s' failed writing: '%s'", this->name.string(), strerror(-err));
+        return err;
+    }
+
+    return NO_ERROR;
+}
+
+// ================================================================================
+CommandSection::CommandSection(int id, const char* first, ...)
+    :Section(id)
+{
+    va_list args;
+    int count = 0;
+
+    va_start(args, first);
+    while (va_arg(args, const char*) != NULL) {
+        count++;
+    }
+    va_end(args);
+
+    mCommand = (const char**)malloc(sizeof(const char*) * count);
+
+    mCommand[0] = first;
+    name = first;
+    name += " ";
+    va_start(args, first);
+    for (int i=0; i<count; i++) {
+        const char* arg = va_arg(args, const char*); 
+        mCommand[i+1] = arg;
+        if (arg != NULL) {
+            name += va_arg(args, const char*);
+            name += " ";
+        }
+    }
+    va_end(args);
+}
+
+CommandSection::~CommandSection()
+{
+}
+
+status_t
+CommandSection::Execute(ReportRequestSet* /*requests*/) const
+{
+    return NO_ERROR;
+}
+
+// ================================================================================
+DumpsysSection::DumpsysSection(int id, const char* service, ...)
+    :WorkerThreadSection(id),
+     mService(service)
+{
+    name = "dumpsys ";
+    name += service;
+
+    va_list args;
+    va_start(args, service);
+    while (true) {
+        const char* arg = va_arg(args, const char*); 
+        if (arg == NULL) {
+            break;
+        }
+        mArgs.add(String16(arg));
+        name += " ";
+        name += arg;
+    }
+    va_end(args);
+}
+
+DumpsysSection::~DumpsysSection()
+{
+}
+
+status_t
+DumpsysSection::BlockingCall(int pipeWriteFd) const
+{
+    // checkService won't wait for the service to show up like getService will.
+    sp<IBinder> service = defaultServiceManager()->checkService(mService);
+    
+    if (service == NULL) {
+        // Returning an error interrupts the entire incident report, so just
+        // log the failure.
+        // TODO: have a meta record inside the report that would log this
+        // failure inside the report, because the fact that we can't find
+        // the service is good data in and of itself. This is running in
+        // another thread so lock that carefully...
+        ALOGW("DumpsysSection: Can't lookup service: %s", String8(mService).string());
+        return NO_ERROR;
+    }
+
+    service->dump(pipeWriteFd, mArgs);
+
+    return NO_ERROR;
+}
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
new file mode 100644
index 0000000..35740e9
--- /dev/null
+++ b/cmds/incidentd/src/Section.h
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+#ifndef SECTIONS_H
+#define SECTIONS_H
+
+#include "FdBuffer.h"
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+using namespace android;
+
+/**
+ * Base class for sections
+ */
+class Section
+{
+public:
+    int id;
+    String8 name;
+
+    Section(int id);
+    virtual ~Section();
+
+    virtual status_t Execute(ReportRequestSet* requests) const = 0;
+
+    status_t WriteHeader(ReportRequestSet* requests, size_t size) const;
+};
+
+/**
+ * Section that reads in a file.
+ */
+class FileSection : public Section
+{
+public:
+    FileSection(int id, const char* filename);
+    virtual ~FileSection();
+
+    virtual status_t Execute(ReportRequestSet* requests) const;
+
+private:
+    const char* mFilename;
+};
+
+/**
+ * Base class for sections that call a command that might need a timeout.
+ */
+class WorkerThreadSection : public Section
+{
+public:
+    WorkerThreadSection(int id);
+    virtual ~WorkerThreadSection();
+
+    virtual status_t Execute(ReportRequestSet* requests) const;
+
+    virtual status_t BlockingCall(int pipeWriteFd) const = 0;
+};
+
+/**
+ * Section that forks and execs a command, and puts stdout as the section.
+ */
+class CommandSection : public Section
+{
+public:
+    CommandSection(int id, const char* first, ...);
+    virtual ~CommandSection();
+
+    virtual status_t Execute(ReportRequestSet* requests) const;
+
+private:
+    const char** mCommand;
+};
+
+/**
+ * Section that calls dumpsys on a system service.
+ */
+class DumpsysSection : public WorkerThreadSection
+{
+public:
+    DumpsysSection(int id, const char* service, ...);
+    virtual ~DumpsysSection();
+
+    virtual status_t BlockingCall(int pipeWriteFd) const;
+
+private:
+    String16 mService;
+    Vector<String16> mArgs;
+};
+
+#endif // SECTIONS_H
+
diff --git a/cmds/incidentd/src/main.cpp b/cmds/incidentd/src/main.cpp
new file mode 100644
index 0000000..3a7511d
--- /dev/null
+++ b/cmds/incidentd/src/main.cpp
@@ -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.
+ */
+
+#define LOG_TAG "incidentd"
+
+#include "IncidentService.h"
+
+#include <binder/IInterface.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+#include <binder/Status.h>
+#include <cutils/log.h>
+#include <utils/Looper.h>
+#include <utils/StrongPointer.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+using namespace android;
+
+// ================================================================================
+int
+main(int /*argc*/, char** /*argv*/)
+{
+    // Set up the looper
+    sp<Looper> looper(Looper::prepare(0 /* opts */));
+
+    // Set up the binder
+    sp<ProcessState> ps(ProcessState::self());
+    ps->setThreadPoolMaxThreadCount(1); // everything is oneway, let it queue and save ram
+    ps->startThreadPool();
+    ps->giveThreadPoolName();
+    IPCThreadState::self()->disableBackgroundScheduling(true);
+
+    // Create the service
+    android::sp<IncidentService> service = new IncidentService(looper);
+    if (defaultServiceManager()->addService(String16("incident"), service) != 0) {
+        ALOGE("Failed to add service");
+        return -1;
+    }
+
+    // Loop forever -- the reports run on this thread in a handler, and the
+    // binder calls remain responsive in their pool of one thread.
+    while (true) {
+        looper->pollAll(-1 /* timeoutMillis */);
+    }
+    ALOGW("incidentd escaped from its loop.");
+
+    return 1;
+}
diff --git a/cmds/incidentd/src/protobuf.cpp b/cmds/incidentd/src/protobuf.cpp
new file mode 100644
index 0000000..cb864fd
--- /dev/null
+++ b/cmds/incidentd/src/protobuf.cpp
@@ -0,0 +1,41 @@
+/*
+ * 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 "protobuf.h"
+
+uint8_t*
+write_raw_varint(uint8_t* buf, uint32_t val)
+{
+    uint8_t* p = buf;
+    while (true) {
+        if ((val & ~0x7F) == 0) {
+            *p++ = (uint8_t)val;
+            return p;
+        } else {
+            *p++ = (uint8_t)((val & 0x7F) | 0x80);
+            val >>= 7;
+        }
+    }
+}
+
+uint8_t*
+write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size)
+{
+    buf = write_raw_varint(buf, (fieldId << 3) | 2);
+    buf = write_raw_varint(buf, size);
+    return buf;
+}
+
diff --git a/cmds/incidentd/src/protobuf.h b/cmds/incidentd/src/protobuf.h
new file mode 100644
index 0000000..a243998
--- /dev/null
+++ b/cmds/incidentd/src/protobuf.h
@@ -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.
+ */
+
+#ifndef PROTOBUF_H
+#define PROTOBUF_H
+
+#include <stdint.h>
+
+/**
+ * Write a varint into the buffer. Return the next position to write at.
+ * There must be 10 bytes in the buffer. The same as EncodedBuffer.writeRawVarint32
+ */
+uint8_t* write_raw_varint(uint8_t* buf, uint32_t val);
+
+/**
+ * Write a protobuf WIRE_TYPE_LENGTH_DELIMITED header. Return the next position to write at.
+ * There must be 20 bytes in the buffer.
+ */
+uint8_t* write_length_delimited_tag_header(uint8_t* buf, uint32_t fieldId, size_t size);
+
+enum {
+    // IncidentProto.header
+    FIELD_ID_INCIDENT_HEADER = 1
+};
+
+#endif // PROTOBUF_H
+
diff --git a/cmds/incidentd/src/report_directory.cpp b/cmds/incidentd/src/report_directory.cpp
new file mode 100644
index 0000000..f60b8ac
--- /dev/null
+++ b/cmds/incidentd/src/report_directory.cpp
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "incidentd"
+
+#include "report_directory.h"
+
+#include <cutils/log.h>
+#include <private/android_filesystem_config.h>
+#include <utils/String8.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <unistd.h>
+
+#include <vector>
+
+using namespace android;
+using namespace std;
+
+status_t
+create_directory(const char* directory)
+{
+    struct stat st;
+    status_t err = NO_ERROR;
+    char* dir = strdup(directory);
+
+    // Skip first slash
+    char* d = dir + 1;
+
+    // Create directories, assigning them to the system user
+    bool last = false;
+    while (!last) {
+        d = strchr(d, '/');
+        if (d != NULL) {
+            *d = '\0';
+        } else {
+            last = true;
+        }
+        if (stat(dir, &st) == 0) {
+            if (!S_ISDIR(st.st_mode)) {
+                err = ALREADY_EXISTS;
+                goto done;
+            }
+        } else {
+            if (mkdir(dir, 0770)) {
+                ALOGE("No incident reports today. "
+                        "Unable to create incident report dir %s: %s", dir,
+                        strerror(errno));
+                err = -errno;
+                goto done;
+            }
+            if (chmod(dir, 0770)) {
+                ALOGE("No incident reports today. "
+                        "Unable to set permissions for incident report dir %s: %s", dir,
+                        strerror(errno));
+                err = -errno;
+                goto done;
+            }
+            if (chown(dir, AID_SYSTEM, AID_SYSTEM)) {
+                ALOGE("No incident reports today. Unable to change ownership of dir %s: %s\n",
+                        dir, strerror(errno));
+                err = -errno;
+                goto done;
+            }
+        }
+        if (!last) {
+            *d++ = '/';
+        }
+    }
+
+    // Ensure that the final directory is owned by the system with 0770. If it isn't
+    // we won't write into it.
+    if (stat(directory, &st) != 0) {
+        ALOGE("No incident reports today. Can't stat: %s", directory);
+        err = -errno;
+        goto done;
+    }
+    if ((st.st_mode & 0777) != 0770) {
+        ALOGE("No incident reports today. Mode is %0o on report directory %s",
+                st.st_mode, directory);
+        err = BAD_VALUE;
+        goto done;
+    }
+    if (st.st_uid != AID_SYSTEM || st.st_gid != AID_SYSTEM) {
+        ALOGE("No incident reports today. Owner is %d and group is %d on report directory %s",
+                st.st_uid, st.st_gid, directory);
+        err = BAD_VALUE;
+        goto done;
+    }
+
+done:
+    free(dir);
+    return err;
+}
+
+static bool
+stat_mtime_cmp(const pair<String8,struct stat>& a, const pair<String8,struct stat>& b)
+{
+    return a.second.st_mtime < b.second.st_mtime;
+}
+
+void
+clean_directory(const char* directory, off_t maxSize, size_t maxCount)
+{
+    DIR* dir;
+    struct dirent* entry;
+    struct stat st;
+
+    vector<pair<String8,struct stat>> files;
+
+    if ((dir = opendir(directory)) == NULL) {
+        ALOGE("Couldn't open incident directory: %s", directory);
+        return;
+    }
+
+    String8 dirbase(String8(directory) + "/");
+
+    off_t totalSize = 0;
+    size_t totalCount = 0;
+
+    // Enumerate, count and add up size
+    while ((entry = readdir(dir)) != NULL) {
+        if (entry->d_name[0] == '.') {
+            continue;
+        }
+        String8 filename = dirbase + entry->d_name;
+        if (stat(filename.string(), &st) != 0) {
+            ALOGE("Unable to stat file %s", filename.string());
+            continue;
+        }
+        if (!S_ISREG(st.st_mode)) {
+            continue;
+        }
+        files.push_back(pair<String8,struct stat>(filename, st));
+
+        totalSize += st.st_size;
+        totalCount++;
+    }
+
+    closedir(dir);
+
+    // Count or size is less than max, then we're done.
+    if (totalSize < maxSize && totalCount < maxCount) {
+        return;
+    }
+
+    // Oldest files first.
+    sort(files.begin(), files.end(), stat_mtime_cmp);
+
+    // Remove files until we're under our limits.
+    for (vector<pair<String8,struct stat>>::iterator it = files.begin();
+            it != files.end() && totalSize >= maxSize && totalCount >= maxCount; it++) {
+        remove(it->first.string());
+        totalSize -= it->second.st_size;
+        totalCount--;
+    }
+}
diff --git a/cmds/incidentd/src/report_directory.h b/cmds/incidentd/src/report_directory.h
new file mode 100644
index 0000000..bed4f86
--- /dev/null
+++ b/cmds/incidentd/src/report_directory.h
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+#ifndef DIRECTORY_CLEANER_H
+#define DIRECTORY_CLEANER_H
+
+#include <utils/Errors.h>
+
+#include <sys/types.h>
+
+using namespace android;
+
+status_t create_directory(const char* directory);
+void clean_directory(const char* directory, off_t maxSize, size_t maxCount);
+
+#endif // DIRECTORY_CLEANER_H
diff --git a/cmds/incidentd/src/section_list.cpp b/cmds/incidentd/src/section_list.cpp
new file mode 100644
index 0000000..b6112ed
--- /dev/null
+++ b/cmds/incidentd/src/section_list.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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 "section_list.h"
+
+//using namespace android::util;
+
+/**
+ * This is the mapping of section IDs to the commands that are run to get those commands.
+ */
+const Section* SECTION_LIST[] = {
+    new DumpsysSection(3000,
+            "fingerprint", "--proto", "--incident", NULL),
+    NULL
+};
+
diff --git a/cmds/incidentd/src/section_list.h b/cmds/incidentd/src/section_list.h
new file mode 100644
index 0000000..c977519
--- /dev/null
+++ b/cmds/incidentd/src/section_list.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#ifndef SECTION_LIST_H
+#define SECTION_LIST_H
+
+#include "Section.h"
+
+/**
+ * This is the mapping of section IDs to the commands that are run to get those commands.
+ */
+extern const Section* SECTION_LIST[];
+
+#endif // SECTION_LIST_H
+
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 94d24e4..db1162a 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -51,6 +51,7 @@
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
+import dalvik.system.BaseDexClassLoader;
 import dalvik.system.VMRuntime;
 
 import java.io.File;
@@ -600,6 +601,40 @@
 
         VMRuntime.registerAppInfo(profileFile.getPath(), mApplicationInfo.dataDir,
                 codePaths.toArray(new String[codePaths.size()]), foreignDexProfilesFile.getPath());
+
+        // Setup the reporter to notify package manager of any relevant dex loads.
+        // At this point the primary apk is loaded and will not be reported.
+        // Anything loaded from now on will be tracked as a potential secondary
+        // or foreign dex file. The goal is to enable:
+        //    1) monitoring and compilation of secondary dex file
+        //    2) track foreign dex file usage (used to determined the
+        //       compilation filter of apks).
+        if (BaseDexClassLoader.getReporter() != DexLoadReporter.INSTANCE) {
+            // Set the dex load reporter if not already set.
+            // Note that during the app's life cycle different LoadedApks may be
+            // created and loaded (e.g. if two different apps share the same runtime).
+            BaseDexClassLoader.setReporter(DexLoadReporter.INSTANCE);
+        }
+    }
+
+    private static class DexLoadReporter implements BaseDexClassLoader.Reporter {
+        private static final DexLoadReporter INSTANCE = new DexLoadReporter();
+
+        private DexLoadReporter() {}
+
+        @Override
+        public void report(List<String> dexPaths) {
+            if (dexPaths.isEmpty()) {
+                return;
+            }
+            String packageName = ActivityThread.currentPackageName();
+            try {
+                ActivityThread.getPackageManager().notifyDexLoad(
+                        packageName, dexPaths, VMRuntime.getRuntime().vmInstructionSet());
+            } catch (RemoteException re) {
+                Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
+            }
+        }
     }
 
     /**
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index e1bd93e..6bddfba 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -96,6 +96,7 @@
 import android.os.IPowerManager;
 import android.os.IRecoverySystem;
 import android.os.IUserManager;
+import android.os.IncidentManager;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RecoverySystem;
@@ -774,6 +775,13 @@
                 return new ContextHubManager(ctx.getOuterContext(),
                   ctx.mMainThread.getHandler().getLooper());
             }});
+
+        registerService(Context.INCIDENT_SERVICE, IncidentManager.class,
+                new CachedServiceFetcher<IncidentManager>() {
+            @Override
+            public IncidentManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+                return new IncidentManager(ctx);
+            }});
     }
 
     /**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index b9783aa..11da8dd 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2747,7 +2747,8 @@
             //@hide: SOUND_TRIGGER_SERVICE,
             SHORTCUT_SERVICE,
             //@hide: CONTEXTHUB_SERVICE,
-            SYSTEM_HEALTH_SERVICE
+            SYSTEM_HEALTH_SERVICE,
+            //@hide: INCIDENT_SERVICE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface ServiceName {}
@@ -3705,6 +3706,12 @@
     public static final String DEVICE_IDENTIFIERS_SERVICE = "device_identifiers";
 
     /**
+     * Service to report a system health "incident"
+     * @hide
+     */
+    public static final String INCIDENT_SERVICE = "incident";
+
+    /**
      * Determine whether the given permission is allowed for a particular
      * process and user ID running in the system.
      *
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index d753a6e..b9b61db 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -458,6 +458,15 @@
     void notifyPackageUse(String packageName, int reason);
 
     /**
+     * Notify the package manager that a list of dex files have been loaded.
+     *
+     * @param loadingPackageName the name of the package who performs the load
+     * @param dexPats the list of the dex files paths that have been loaded
+     * @param loaderIsa the ISA of the loader process
+     */
+    void notifyDexLoad(String loadingPackageName, in List<String> dexPaths, String loaderIsa);
+
+    /**
      * Ask the package manager to perform dex-opt (if needed) on the given
      * package if it already hasn't done so.
      *
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 74aded6..32bf66a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -3789,6 +3789,8 @@
                     ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE;
         }
 
+        final boolean hasVisibleToEphemeral =
+                sa.hasValue(R.styleable.AndroidManifestActivity_visibleToEphemeral);
         final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
         final boolean visibleToEphemeral = isEphemeral
                 || sa.getBoolean(R.styleable.AndroidManifestActivity_visibleToEphemeral, false);
@@ -3827,7 +3829,7 @@
                     return null;
                 }
                 intent.setEphemeral(isEphemeral);
-                intent.setVisibleToEphemeral(visibleToEphemeral);
+                intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
                 if (intent.countActions() == 0) {
                     Slog.w(TAG, "No actions in intent filter at "
                             + mArchiveSourcePath + " "
@@ -3835,6 +3837,10 @@
                 } else {
                     a.intents.add(intent);
                 }
+                // adjust activity flags when we implicitly expose it via a browsable filter
+                if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) {
+                    a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL;
+                }
             } else if (!receiver && parser.getName().equals("preferred")) {
                 ActivityIntentInfo intent = new ActivityIntentInfo(a);
                 if (!parseIntent(res, parser, false /*allowGlobs*/, false /*allowAutoVerify*/,
@@ -3842,7 +3848,7 @@
                     return null;
                 }
                 intent.setEphemeral(isEphemeral);
-                intent.setVisibleToEphemeral(visibleToEphemeral);
+                intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
                 if (intent.countActions() == 0) {
                     Slog.w(TAG, "No actions in preferred at "
                             + mArchiveSourcePath + " "
@@ -3853,6 +3859,10 @@
                     }
                     owner.preferredActivityFilters.add(intent);
                 }
+                // adjust activity flags when we implicitly expose it via a browsable filter
+                if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) {
+                    a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL;
+                }
             } else if (parser.getName().equals("meta-data")) {
                 if ((a.metaData = parseMetaData(res, parser, a.metaData,
                         outError)) == null) {
@@ -4093,6 +4103,7 @@
             }
         }
 
+        // TODO add visibleToInstantApp attribute to activity alias
         final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
         final boolean visibleToEphemeral = isEphemeral
                 || ((a.info.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) != 0);
@@ -4124,9 +4135,14 @@
                             + parser.getPositionDescription());
                 } else {
                     intent.setEphemeral(isEphemeral);
-                    intent.setVisibleToEphemeral(visibleToEphemeral);
+                    intent.setVisibleToEphemeral(visibleToEphemeral
+                            || isWebBrowsableIntent(intent));
                     a.intents.add(intent);
                 }
+                // adjust activity flags when we implicitly expose it via a browsable filter
+                if (intent.isVisibleToEphemeral()) {
+                    a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL;
+                }
             } else if (parser.getName().equals("meta-data")) {
                 if ((a.metaData=parseMetaData(res, parser, a.metaData,
                         outError)) == null) {
@@ -4262,6 +4278,8 @@
                     ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE;
         }
 
+        final boolean hasVisibleToEphemeral =
+                sa.hasValue(R.styleable.AndroidManifestProvider_visibleToEphemeral);
         final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
         final boolean visibleToEphemeral = isEphemeral
                 || sa.getBoolean(R.styleable.AndroidManifestProvider_visibleToEphemeral, false);
@@ -4291,7 +4309,8 @@
         }
         p.info.authority = cpname.intern();
 
-        if (!parseProviderTags(res, parser, isEphemeral, visibleToEphemeral, p, outError)) {
+        if (!parseProviderTags(
+                res, parser, isEphemeral, hasVisibleToEphemeral, visibleToEphemeral, p, outError)) {
             return null;
         }
 
@@ -4299,8 +4318,9 @@
     }
 
     private boolean parseProviderTags(Resources res, XmlResourceParser parser,
-            boolean isEphemeral, boolean visibleToEphemeral, Provider outInfo, String[] outError)
-            throws XmlPullParserException, IOException {
+            boolean isEphemeral, boolean hasVisibleToEphemeral, boolean visibleToEphemeral,
+            Provider outInfo, String[] outError)
+                    throws XmlPullParserException, IOException {
         int outerDepth = parser.getDepth();
         int type;
         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -4317,8 +4337,12 @@
                     return false;
                 }
                 intent.setEphemeral(isEphemeral);
-                intent.setVisibleToEphemeral(visibleToEphemeral);
+                intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
                 outInfo.intents.add(intent);
+                // adjust provider flags when we implicitly expose it via a browsable filter
+                if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) {
+                    outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_EPHEMERAL;
+                }
 
             } else if (parser.getName().equals("meta-data")) {
                 if ((outInfo.metaData=parseMetaData(res, parser,
@@ -4565,6 +4589,8 @@
                     ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE;
         }
 
+        final boolean hasVisibleToEphemeral =
+                sa.hasValue(R.styleable.AndroidManifestService_visibleToEphemeral);
         final boolean isEphemeral = ((flags & PARSE_IS_EPHEMERAL) != 0);
         final boolean visibleToEphemeral = isEphemeral
                 || sa.getBoolean(R.styleable.AndroidManifestService_visibleToEphemeral, false);
@@ -4600,8 +4626,11 @@
                     return null;
                 }
                 intent.setEphemeral(isEphemeral);
-                intent.setVisibleToEphemeral(visibleToEphemeral);
-
+                intent.setVisibleToEphemeral(visibleToEphemeral || isWebBrowsableIntent(intent));
+                // adjust activity flags when we implicitly expose it via a browsable filter
+                if (!hasVisibleToEphemeral && intent.isVisibleToEphemeral()) {
+                    s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_EPHEMERAL;
+                }
                 s.intents.add(intent);
             } else if (parser.getName().equals("meta-data")) {
                 if ((s.metaData=parseMetaData(res, parser, s.metaData,
@@ -4629,6 +4658,12 @@
         return s;
     }
 
+    private boolean isWebBrowsableIntent(IntentInfo intent) {
+        return intent.hasAction(Intent.ACTION_VIEW)
+                && intent.hasCategory(Intent.CATEGORY_BROWSABLE)
+                && (intent.hasDataScheme("http") || intent.hasDataScheme("https"));
+    }
+
     private boolean parseAllMetaData(Resources res, XmlResourceParser parser, String tag,
             Component<?> outInfo, String[] outError) throws XmlPullParserException, IOException {
         int outerDepth = parser.getDepth();
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index 56f914e..a4d8916 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -129,6 +129,10 @@
             | CLONE_REMOVE_RES_NAMES;
 
     /** @hide */
+    public static final int CLONE_REMOVE_FOR_LAUNCHER_APPROVAL = CLONE_REMOVE_INTENT
+            | CLONE_REMOVE_RES_NAMES;
+
+    /** @hide */
     @IntDef(flag = true,
             value = {
                     CLONE_REMOVE_ICON,
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index 865b8dd..4e606ef 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -183,7 +183,7 @@
         if (app == null) {
             return null;
         }
-        return app.mPackageName;
+        return app.packageName;
     }
 
     /**
@@ -272,19 +272,11 @@
      * @hide
      */
     public boolean requestScores(NetworkKey[] networks) throws SecurityException {
-        String activeScorer = getActiveScorerPackage();
-        if (activeScorer == null) {
-            return false;
+        try {
+            return mService.requestScores(networks);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
         }
-        Intent intent = new Intent(ACTION_SCORE_NETWORKS);
-        intent.setPackage(activeScorer);
-        intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        intent.putExtra(EXTRA_NETWORKS_TO_SCORE, networks);
-        // A scorer should never become active if its package doesn't hold SCORE_NETWORKS, but
-        // ensure the package still holds it to be extra safe.
-        // TODO: http://b/23422763
-        mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM, Manifest.permission.SCORE_NETWORKS);
-        return true;
     }
 
     /**
@@ -344,6 +336,8 @@
     /**
      * Request a recommendation for which network to connect to.
      *
+     * <p>It is not safe to call this method from the main thread.
+     *
      * @param request a {@link RecommendationRequest} instance containing additional
      *                request details
      * @return a {@link RecommendationResult} instance containing the recommended network
diff --git a/core/java/android/net/NetworkScorerAppManager.java b/core/java/android/net/NetworkScorerAppManager.java
index ebb31c9..4282ca7 100644
--- a/core/java/android/net/NetworkScorerAppManager.java
+++ b/core/java/android/net/NetworkScorerAppManager.java
@@ -19,160 +19,176 @@
 import android.Manifest;
 import android.Manifest.permission;
 import android.annotation.Nullable;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
-
+import com.android.internal.R;
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
 /**
- * Internal class for managing the primary network scorer application.
- *
- * TODO: Rename this to something more generic.
+ * Internal class for discovering and managing the network scorer/recommendation application.
  *
  * @hide
  */
 public class NetworkScorerAppManager {
     private static final String TAG = "NetworkScorerAppManager";
-
-    private static final Intent SCORE_INTENT =
-            new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS);
-
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private final Context mContext;
 
     public NetworkScorerAppManager(Context context) {
       mContext = context;
     }
 
+    /**
+     * Holds metadata about a discovered network scorer/recommendation application.
+     */
     public static class NetworkScorerAppData {
         /** Package name of this scorer app. */
-        public final String mPackageName;
+        public final String packageName;
 
         /** UID of the scorer app. */
-        public final int mPackageUid;
-
-        /** Name of this scorer app for display. */
-        public final CharSequence mScorerName;
+        public final int packageUid;
 
         /**
-         * Optional class name of a configuration activity. Null if none is set.
-         *
-         * @see NetworkScoreManager#ACTION_CUSTOM_ENABLE
+         * Name of the recommendation service we can bind to.
          */
-        public final String mConfigurationActivityClassName;
+        public final String recommendationServiceClassName;
 
-        /**
-         * Optional class name of the scoring service we can bind to. Null if none is set.
-         */
-        public final String mScoringServiceClassName;
-
-        public NetworkScorerAppData(String packageName, int packageUid, CharSequence scorerName,
-                @Nullable String configurationActivityClassName,
-                @Nullable String scoringServiceClassName) {
-            mScorerName = scorerName;
-            mPackageName = packageName;
-            mPackageUid = packageUid;
-            mConfigurationActivityClassName = configurationActivityClassName;
-            mScoringServiceClassName = scoringServiceClassName;
+        public NetworkScorerAppData(String packageName, int packageUid,
+                String recommendationServiceClassName) {
+            this.packageName = packageName;
+            this.packageUid = packageUid;
+            this.recommendationServiceClassName = recommendationServiceClassName;
         }
 
         @Override
         public String toString() {
             final StringBuilder sb = new StringBuilder("NetworkScorerAppData{");
-            sb.append("mPackageName='").append(mPackageName).append('\'');
-            sb.append(", mPackageUid=").append(mPackageUid);
-            sb.append(", mScorerName=").append(mScorerName);
-            sb.append(", mConfigurationActivityClassName='").append(mConfigurationActivityClassName)
-                    .append('\'');
-            sb.append(", mScoringServiceClassName='").append(mScoringServiceClassName).append('\'');
+            sb.append("mPackageName='").append(packageName).append('\'');
+            sb.append(", packageUid=").append(packageUid);
+            sb.append(", recommendationServiceClassName='")
+                    .append(recommendationServiceClassName).append('\'');
             sb.append('}');
             return sb.toString();
         }
     }
 
     /**
-     * Returns the list of available scorer apps.
+     * @return A {@link NetworkScorerAppData} instance containing information about the
+     *         best configured network recommendation provider installed or {@code null}
+     *         if none of the configured packages can recommend networks.
      *
-     * <p>A network scorer is any application which:
+     * <p>A network recommendation provider is any application which:
      * <ul>
+     * <li>Is listed in the <code>config_networkRecommendationPackageNames</code> config.
      * <li>Declares the {@link android.Manifest.permission#SCORE_NETWORKS} permission.
-     * <li>Includes a receiver for {@link NetworkScoreManager#ACTION_SCORE_NETWORKS} guarded by the
-     *     {@link android.Manifest.permission#BROADCAST_NETWORK_PRIVILEGED} permission.
+     * <li>Includes a Service for {@link NetworkScoreManager#ACTION_RECOMMEND_NETWORKS}.
      * </ul>
-     *
-     * @return the list of scorers, or the empty list if there are no valid scorers.
      */
-    public Collection<NetworkScorerAppData> getAllValidScorers() {
-        // Network scorer apps can only run as the primary user so exit early if we're not the
-        // primary user.
+    public NetworkScorerAppData getNetworkRecommendationProviderData() {
+        // Network recommendation apps can only run as the primary user right now.
+        // http://b/23422763
         if (UserHandle.getCallingUserId() != UserHandle.USER_SYSTEM) {
+            return null;
+        }
+
+        final List<String> potentialPkgs = getPotentialRecommendationProviderPackages();
+        if (potentialPkgs.isEmpty()) {
+            if (DEBUG) {
+                Log.d(TAG, "No Network Recommendation Providers specified.");
+            }
+            return null;
+        }
+
+        final PackageManager pm = mContext.getPackageManager();
+        for (int i = 0; i < potentialPkgs.size(); i++) {
+            final String potentialPkg = potentialPkgs.get(i);
+
+            // Look for the recommendation service class and required receiver.
+            final ResolveInfo resolveServiceInfo = findRecommendationService(potentialPkg);
+            if (resolveServiceInfo != null) {
+                return new NetworkScorerAppData(potentialPkg,
+                    resolveServiceInfo.serviceInfo.applicationInfo.uid,
+                    resolveServiceInfo.serviceInfo.name);
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, potentialPkg + " does not have the required components, skipping.");
+                }
+            }
+        }
+
+        // None of the configured packages are valid.
+        return null;
+    }
+
+    /**
+     * @return A priority order list of package names that have been granted the
+     *         permission needed for them to act as a network recommendation provider.
+     *         The packages in the returned list may not contain the other required
+     *         network recommendation provider components so additional checks are required
+     *         before making a package the network recommendation provider.
+     */
+    public List<String> getPotentialRecommendationProviderPackages() {
+        final String[] packageArray = mContext.getResources().getStringArray(
+                R.array.config_networkRecommendationPackageNames);
+        if (packageArray == null || packageArray.length == 0) {
+            if (DEBUG) {
+                Log.d(TAG, "No Network Recommendation Providers specified.");
+            }
             return Collections.emptyList();
         }
 
-        List<NetworkScorerAppData> scorers = new ArrayList<>();
-        PackageManager pm = mContext.getPackageManager();
-        // Only apps installed under the primary user of the device can be scorers.
-        // TODO: http://b/23422763
-        List<ResolveInfo> receivers =
-                pm.queryBroadcastReceiversAsUser(SCORE_INTENT, 0 /* flags */, UserHandle.USER_SYSTEM);
-        for (ResolveInfo receiver : receivers) {
-            // This field is a misnomer, see android.content.pm.ResolveInfo#activityInfo
-            final ActivityInfo receiverInfo = receiver.activityInfo;
-            if (receiverInfo == null) {
-                // Should never happen with queryBroadcastReceivers, but invalid nonetheless.
-                continue;
-            }
-            if (!permission.BROADCAST_NETWORK_PRIVILEGED.equals(receiverInfo.permission)) {
-                // Receiver doesn't require the BROADCAST_NETWORK_PRIVILEGED permission, which
-                // means anyone could trigger network scoring and flood the framework with score
-                // requests.
-                continue;
-            }
-            if (pm.checkPermission(permission.SCORE_NETWORKS, receiverInfo.packageName) !=
-                    PackageManager.PERMISSION_GRANTED) {
-                // Application doesn't hold the SCORE_NETWORKS permission, so the user never
-                // approved it as a network scorer.
-                continue;
-            }
-
-            // Optionally, this package may specify a configuration activity.
-            String configurationActivityClassName = null;
-            Intent intent = new Intent(NetworkScoreManager.ACTION_CUSTOM_ENABLE);
-            intent.setPackage(receiverInfo.packageName);
-            List<ResolveInfo> configActivities = pm.queryIntentActivities(intent, 0 /* flags */);
-            if (configActivities != null && !configActivities.isEmpty()) {
-                ActivityInfo activityInfo = configActivities.get(0).activityInfo;
-                if (activityInfo != null) {
-                    configurationActivityClassName = activityInfo.name;
-                }
-            }
-
-            // Find the scoring service class we can bind to, if any.
-            String scoringServiceClassName = null;
-            Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_SCORE_NETWORKS);
-            serviceIntent.setPackage(receiverInfo.packageName);
-            ResolveInfo resolveServiceInfo = pm.resolveService(serviceIntent, 0 /* flags */);
-            if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) {
-                scoringServiceClassName = resolveServiceInfo.serviceInfo.name;
-            }
-
-            // NOTE: loadLabel will attempt to load the receiver's label and fall back to the
-            // app label if none is present.
-            scorers.add(new NetworkScorerAppData(receiverInfo.packageName,
-                    receiverInfo.applicationInfo.uid, receiverInfo.loadLabel(pm),
-                    configurationActivityClassName, scoringServiceClassName));
+        if (VERBOSE) {
+            Log.d(TAG, "Configured packages: " + TextUtils.join(", ", packageArray));
         }
 
-        return scorers;
+        List<String> packages = new ArrayList<>();
+        final PackageManager pm = mContext.getPackageManager();
+        for (String potentialPkg : packageArray) {
+            if (pm.checkPermission(permission.SCORE_NETWORKS, potentialPkg)
+                    == PackageManager.PERMISSION_GRANTED) {
+                packages.add(potentialPkg);
+            } else {
+                if (DEBUG) {
+                    Log.d(TAG, potentialPkg + " has not been granted " + permission.SCORE_NETWORKS
+                            + ", skipping.");
+                }
+            }
+        }
+
+        return packages;
+    }
+
+    private ResolveInfo findRecommendationService(String packageName) {
+        final PackageManager pm = mContext.getPackageManager();
+        final int resolveFlags = 0;
+
+        final Intent serviceIntent = new Intent(NetworkScoreManager.ACTION_RECOMMEND_NETWORKS);
+        serviceIntent.setPackage(packageName);
+        final ResolveInfo resolveServiceInfo =
+                pm.resolveService(serviceIntent, resolveFlags);
+
+        if (VERBOSE) {
+            Log.d(TAG, "Resolved " + serviceIntent + " to " + resolveServiceInfo);
+        }
+
+        if (resolveServiceInfo != null && resolveServiceInfo.serviceInfo != null) {
+            return resolveServiceInfo;
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, packageName + " does not have a service for " + serviceIntent);
+        }
+        return null;
     }
 
     /**
@@ -182,10 +198,15 @@
      *     selected) or if the previously-set scorer is no longer a valid scorer app (e.g. because
      *     it was disabled or uninstalled).
      */
+    @Nullable
     public NetworkScorerAppData getActiveScorer() {
-        String scorerPackage = Settings.Global.getString(mContext.getContentResolver(),
-                Settings.Global.NETWORK_SCORER_APP);
-        return getScorer(scorerPackage);
+        if (isNetworkRecommendationsDisabled()) {
+            // If recommendations are disabled then there can't be an active scorer.
+            return null;
+        }
+
+        // Otherwise return the recommendation provider (which may be null).
+        return getNetworkRecommendationProviderData();
     }
 
     /**
@@ -195,33 +216,13 @@
      *
      * @param packageName the packageName of the new scorer to use. If null, scoring will be
      *     disabled. Otherwise, the scorer will only be set if it is a valid scorer application.
-     * @return true if the scorer was changed, or false if the package is not a valid scorer.
+     * @return true if the scorer was changed, or false if the package is not a valid scorer or
+     *         a valid network recommendation provider exists.
+     * @deprecated Scorers are now selected from a configured list.
      */
+    @Deprecated
     public boolean setActiveScorer(String packageName) {
-        String oldPackageName = Settings.Global.getString(mContext.getContentResolver(),
-                Settings.Global.NETWORK_SCORER_APP);
-        if (TextUtils.equals(oldPackageName, packageName)) {
-            // No change.
-            return true;
-        }
-
-        Log.i(TAG, "Changing network scorer from " + oldPackageName + " to " + packageName);
-
-        if (packageName == null) {
-            Settings.Global.putString(mContext.getContentResolver(),
-                    Settings.Global.NETWORK_SCORER_APP, null);
-            return true;
-        } else {
-            // We only make the change if the new package is valid.
-            if (getScorer(packageName) != null) {
-                Settings.Global.putString(mContext.getContentResolver(),
-                        Settings.Global.NETWORK_SCORER_APP, packageName);
-                return true;
-            } else {
-                Log.w(TAG, "Requested network scorer is not valid: " + packageName);
-                return false;
-            }
-        }
+        return false;
     }
 
     /** Determine whether the application with the given UID is the enabled scorer. */
@@ -230,7 +231,7 @@
         if (defaultApp == null) {
             return false;
         }
-        if (callingUid != defaultApp.mPackageUid) {
+        if (callingUid != defaultApp.packageUid) {
             return false;
         }
         // To be extra safe, ensure the caller holds the SCORE_NETWORKS permission. It always
@@ -239,17 +240,9 @@
                 PackageManager.PERMISSION_GRANTED;
     }
 
-    /** Returns the {@link NetworkScorerAppData} for the given app, or null if it's not a scorer. */
-    public NetworkScorerAppData getScorer(String packageName) {
-        if (TextUtils.isEmpty(packageName)) {
-            return null;
-        }
-        Collection<NetworkScorerAppData> applications = getAllValidScorers();
-        for (NetworkScorerAppData app : applications) {
-            if (packageName.equals(app.mPackageName)) {
-                return app;
-            }
-        }
-        return null;
+    private boolean isNetworkRecommendationsDisabled() {
+        final ContentResolver cr = mContext.getContentResolver();
+        // A value of 1 indicates enabled.
+        return Settings.Global.getInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) != 1;
     }
 }
diff --git a/core/java/android/net/RecommendationRequest.java b/core/java/android/net/RecommendationRequest.java
index 05ca1aa..a96f90d 100644
--- a/core/java/android/net/RecommendationRequest.java
+++ b/core/java/android/net/RecommendationRequest.java
@@ -105,7 +105,16 @@
     }
 
     protected RecommendationRequest(Parcel in) {
-        mScanResults = (ScanResult[]) in.readParcelableArray(ScanResult.class.getClassLoader());
+        final int resultCount = in.readInt();
+        if (resultCount > 0) {
+            mScanResults = new ScanResult[resultCount];
+            for (int i = 0; i < resultCount; i++) {
+                mScanResults[i] = in.readParcelable(ScanResult.class.getClassLoader());
+            }
+        } else {
+            mScanResults = null;
+        }
+
         mCurrentSelectedConfig = in.readParcelable(WifiConfiguration.class.getClassLoader());
         mRequiredCapabilities = in.readParcelable(NetworkCapabilities.class.getClassLoader());
     }
@@ -117,7 +126,14 @@
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
-        dest.writeParcelableArray(mScanResults, flags);
+        if (mScanResults != null) {
+            dest.writeInt(mScanResults.length);
+            for (int i = 0; i < mScanResults.length; i++) {
+                dest.writeParcelable(mScanResults[i], flags);
+            }
+        } else {
+            dest.writeInt(0);
+        }
         dest.writeParcelable(mCurrentSelectedConfig, flags);
         dest.writeParcelable(mRequiredCapabilities, flags);
     }
diff --git a/core/java/android/os/DropBoxManager.aidl b/core/java/android/os/DropBoxManager.aidl
index 6474ec2..241e93b 100644
--- a/core/java/android/os/DropBoxManager.aidl
+++ b/core/java/android/os/DropBoxManager.aidl
@@ -16,4 +16,4 @@
 
 package android.os;
 
-parcelable DropBoxManager.Entry;
+parcelable DropBoxManager.Entry cpp_header "android/os/DropBoxManager.h";
diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl
new file mode 100644
index 0000000..1a76648
--- /dev/null
+++ b/core/java/android/os/IIncidentManager.aidl
@@ -0,0 +1,49 @@
+/**
+ * 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.os;
+
+import android.os.IIncidentReportCompletedListener;
+import android.os.IIncidentReportStatusListener;
+import android.os.IncidentReportArgs;
+
+/**
+  * Binder interface to report system health incidents.
+  * {@hide}
+  */
+oneway interface IIncidentManager {
+
+    /**
+     * Takes a report with the given args, reporting status to the optional listener.
+     *
+     * When the report is completed, the system report listener will be notified.
+     */
+    void reportIncident(in IncidentReportArgs args);
+
+    /**
+     * Takes a report with the given args, reporting status to the optional listener.
+     *
+     * When the report is completed, the system report listener will be notified.
+     */
+    void reportIncidentToStream(in IncidentReportArgs args,
+            @nullable IIncidentReportStatusListener listener,
+            FileDescriptor stream);
+
+    /**
+     * Tell the incident daemon that the android system server is up and running.
+     */
+    void systemRunning();
+}
diff --git a/core/java/android/os/IIncidentReportCompletedListener.aidl b/core/java/android/os/IIncidentReportCompletedListener.aidl
new file mode 100644
index 0000000..2d66bf6
--- /dev/null
+++ b/core/java/android/os/IIncidentReportCompletedListener.aidl
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+/**
+  * Listener for incident report status
+  *
+  * {@hide}
+  */
+oneway interface IIncidentReportCompletedListener {
+    /**
+     * Called when there has been an incident report.
+     *
+     * The system service implementing this method should delete or move the file
+     * after it is finished with it.
+     */
+    void onIncidentReport(String filename);
+}
diff --git a/core/java/android/os/IIncidentReportStatusListener.aidl b/core/java/android/os/IIncidentReportStatusListener.aidl
new file mode 100644
index 0000000..7be2ac8
--- /dev/null
+++ b/core/java/android/os/IIncidentReportStatusListener.aidl
@@ -0,0 +1,34 @@
+/**
+ * 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.os;
+
+/**
+  * Listener for incident report status
+  *
+  * {@hide}
+  */
+oneway interface IIncidentReportStatusListener {
+    const int STATUS_STARTING = 1;
+    const int STATUS_FINISHED = 2;
+
+    void onReportStarted();
+
+    void onReportSectionStatus(int section, int status);
+
+    void onReportFinished();
+    void onReportFailed();
+}
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
new file mode 100644
index 0000000..976d594
--- /dev/null
+++ b/core/java/android/os/IncidentManager.java
@@ -0,0 +1,114 @@
+/*
+ * 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.os;
+
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.Context;
+import android.os.IIncidentManager;
+import android.os.ServiceManager;
+import android.provider.Settings;
+import android.util.Slog;
+
+/**
+ * Class to take an incident report.
+ *
+ * @hide
+ */
+@SystemApi
+@TestApi
+public class IncidentManager {
+    private static final String TAG = "incident";
+
+    private Context mContext;
+
+    /**
+     * @hide
+     */
+    public IncidentManager(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Take an incident report and put it in dropbox.
+     */
+    public void reportIncident(IncidentReportArgs args) {
+        final IIncidentManager service = IIncidentManager.Stub.asInterface(
+                ServiceManager.getService("incident"));
+        if (service == null) {
+            Slog.e(TAG, "reportIncident can't find incident binder service");
+            return;
+        }
+
+        try {
+            service.reportIncident(args);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "reportIncident failed", ex);
+        }
+    }
+
+    /**
+     * Convenience method to trigger an incident report and put it in dropbox.
+     * <p>
+     * The fields that are reported will be looked up in the system setting named by
+     * the settingName parameter.  The setting must match one of these patterns:
+     *      The string "disabled": The report will not be taken.
+     *      The string "all": The report will taken with all sections.
+     *      The string "none": The report will taken with no sections, but with the header.
+     *      A comma separated list of field numbers: The report will have these fields.
+     * <p>
+     * The header parameter will be added as a header for the incident report.  Fill in a
+     * {@link android.util.proto.ProtoOutputStream ProtoOutputStream}, and then call the
+     * {@link android.util.proto.ProtoOutputStream#bytes bytes()} method to retrieve
+     * the encoded data for the header.
+     */
+    public void reportIncident(String settingName, byte[] headerProto) {
+        // Sections
+        String setting = Settings.System.getString(mContext.getContentResolver(), settingName);
+        IncidentReportArgs args;
+        try {
+            args = IncidentReportArgs.parseSetting(setting);
+        } catch (IllegalArgumentException ex) {
+            Slog.w(TAG, "Bad value for incident report setting '" + settingName + "'", ex);
+            return;
+        }
+        if (args == null) {
+            Slog.i(TAG, "Incident report requested but disabled: " + settingName);
+            return;
+        }
+
+        // Header
+        args.addHeader(headerProto);
+
+        // Look up the service
+        final IIncidentManager service = IIncidentManager.Stub.asInterface(
+                ServiceManager.getService("incident"));
+        if (service == null) {
+            Slog.e(TAG, "reportIncident can't find incident binder service");
+            return;
+        }
+
+        // Call the service
+        Slog.i(TAG, "Taking incident report: " + settingName);
+        try {
+            service.reportIncident(args);
+        } catch (RemoteException ex) {
+            Slog.e(TAG, "reportIncident failed", ex);
+        }
+    }
+}
+
diff --git a/core/java/android/os/IncidentReportArgs.aidl b/core/java/android/os/IncidentReportArgs.aidl
new file mode 100644
index 0000000..bbddf59
--- /dev/null
+++ b/core/java/android/os/IncidentReportArgs.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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.os;
+
+parcelable IncidentReportArgs cpp_header "android/os/IncidentReportArgs.h";
+
diff --git a/core/java/android/os/IncidentReportArgs.java b/core/java/android/os/IncidentReportArgs.java
new file mode 100644
index 0000000..ce2ae10
--- /dev/null
+++ b/core/java/android/os/IncidentReportArgs.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2005 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.os;
+
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.content.Intent;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.IntArray;
+
+import java.util.ArrayList;
+
+/**
+ * The arguments for an incident report.
+ * {@hide}
+ */
+@SystemApi
+@TestApi
+public final class IncidentReportArgs implements Parcelable {
+
+    private final IntArray mSections = new IntArray();
+    private final ArrayList<byte[]> mHeaders = new ArrayList<byte[]>();
+    private boolean mAll;
+
+    /**
+     * Construct an incident report args with no fields.
+     */
+    public IncidentReportArgs() {
+    }
+
+    /**
+     * Construct an incdent report args from the given parcel.
+     */
+    public IncidentReportArgs(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mAll ? 1 : 0);
+
+        int N = mSections.size();
+        out.writeInt(N);
+        for (int i=0; i<N; i++) {
+            out.writeInt(mSections.get(i));
+        }
+
+        N = mHeaders.size();
+        out.writeInt(N);
+        for (int i=0; i<N; i++) {
+            out.writeByteArray(mHeaders.get(i));
+        }
+    }
+
+    public void readFromParcel(Parcel in) {
+        mAll = in.readInt() != 0;
+
+        mSections.clear();
+        int N = in.readInt();
+        for (int i=0; i<N; i++) {
+            mSections.add(in.readInt());
+        }
+
+        mHeaders.clear();
+        N = in.readInt();
+        for (int i=0; i<N; i++) {
+            mHeaders.add(in.createByteArray());
+        }
+    }
+
+    public static final Parcelable.Creator<IncidentReportArgs> CREATOR
+            = new Parcelable.Creator<IncidentReportArgs>() {
+        public IncidentReportArgs createFromParcel(Parcel in) {
+            return new IncidentReportArgs(in);
+        }
+
+        public IncidentReportArgs[] newArray(int size) {
+            return new IncidentReportArgs[size];
+        }
+    };
+
+    /**
+     * Print this report as a string.
+     */
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("Incident(");
+        if (mAll) {
+            sb.append("all");
+        } else {
+            final int N = mSections.size();
+            if (N > 0) {
+                sb.append(mSections.get(0));
+            }
+            for (int i=1; i<N; i++) {
+                sb.append(" ");
+                sb.append(mSections.get(i));
+            }
+        }
+        sb.append(", ");
+        sb.append(mHeaders.size());
+        sb.append(" headers)");
+        return sb.toString();
+    }
+
+    /**
+     * Set this incident report to include all fields.
+     */
+    public void setAll(boolean all) {
+        mAll = all;
+        if (all) {
+            mSections.clear();
+        }
+    }
+
+    /**
+     * Add this section to the incident report.
+     */
+    public void addSection(int section) {
+        if (!mAll) {
+            mSections.add(section);
+        }
+    }
+
+    /**
+     * Returns whether the incident report will include all fields.
+     */
+    public boolean isAll() {
+        return mAll;
+    }
+
+    /**
+     * Returns whether this section will be included in the incident report.
+     */
+    public boolean containsSection(int section) {
+        return mAll || mSections.indexOf(section) >= 0;
+    }
+
+    public int sectionCount() {
+        return mSections.size();
+    }
+
+    public void addHeader(byte[] header) {
+        mHeaders.add(header);
+    }
+
+    /**
+     * Parses an incident report config as described in the system setting.
+     *
+     * @see IncidentManager#reportIncident
+     */
+    public static IncidentReportArgs parseSetting(String setting)
+            throws IllegalArgumentException {
+        if (setting == null || setting.length() == 0) {
+            return null;
+        }
+        setting = setting.trim();
+        if (setting.length() == 0 || "disabled".equals(setting)) {
+            return null;
+        }
+
+        final IncidentReportArgs args = new IncidentReportArgs();
+
+        if ("all".equals(setting)) {
+            args.setAll(true);
+            return args;
+        } else if ("none".equals(setting)) {
+            return args;
+        }
+
+        final String[] splits = setting.split(",");
+        final int N = splits.length;
+        for (int i=0; i<N; i++) {
+            final String str = splits[i].trim();
+            if (str.length() == 0) {
+                continue;
+            }
+            int section;
+            try {
+                section = Integer.parseInt(str);
+            } catch (NumberFormatException ex) {
+                throw new IllegalArgumentException("Malformed setting. Bad integer at section"
+                        + " index " + i + ": section='" + str + "' setting='" + setting + "'");
+            }
+            if (section < 1) {
+                throw new IllegalArgumentException("Malformed setting. Illegal section at"
+                        + " index " + i + ": section='" + str + "' setting='" + setting + "'");
+            }
+            args.addSection(section);
+        }
+
+        return args;
+    }
+}
+
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 4eee854..9cd1a42 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -398,6 +398,10 @@
      * make easily identifyable processes even if you are using the same base
      * <var>processClass</var> to start them.
      * 
+     * When invokeWith is not null, the process will be started as a fresh app
+     * and not a zygote fork. Note that this is only allowed for uid 0 or when
+     * debugFlags contains DEBUG_ENABLE_DEBUGGER.
+     *
      * @param processClass The class to use as the process's main entry
      *                     point.
      * @param niceName A more readable name to use for the process.
@@ -410,6 +414,7 @@
      * @param abi non-null the ABI this app should be started with.
      * @param instructionSet null-ok the instruction set to use.
      * @param appDataDir null-ok the data directory of the app.
+     * @param invokeWith null-ok the command to invoke with.
      * @param zygoteArgs Additional arguments to supply to the zygote process.
      * 
      * @return An object that describes the result of the attempt to start the process.
@@ -426,10 +431,11 @@
                                   String abi,
                                   String instructionSet,
                                   String appDataDir,
+                                  String invokeWith,
                                   String[] zygoteArgs) {
         return zygoteProcess.start(processClass, niceName, uid, gid, gids,
                     debugFlags, mountExternal, targetSdkVersion, seInfo,
-                    abi, instructionSet, appDataDir, zygoteArgs);
+                    abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
     }
 
     /** @hide */
@@ -442,10 +448,11 @@
                                   String abi,
                                   String instructionSet,
                                   String appDataDir,
+                                  String invokeWith,
                                   String[] zygoteArgs) {
         return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids,
                     debugFlags, mountExternal, targetSdkVersion, seInfo,
-                    abi, instructionSet, appDataDir, zygoteArgs);
+                    abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
     }
 
     /**
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index c45fe5a..5ac33a1 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -170,6 +170,10 @@
      * make easily identifyable processes even if you are using the same base
      * <var>processClass</var> to start them.
      *
+     * When invokeWith is not null, the process will be started as a fresh app
+     * and not a zygote fork. Note that this is only allowed for uid 0 or when
+     * debugFlags contains DEBUG_ENABLE_DEBUGGER.
+     *
      * @param processClass The class to use as the process's main entry
      *                     point.
      * @param niceName A more readable name to use for the process.
@@ -182,6 +186,7 @@
      * @param abi non-null the ABI this app should be started with.
      * @param instructionSet null-ok the instruction set to use.
      * @param appDataDir null-ok the data directory of the app.
+     * @param invokeWith null-ok the command to invoke with.
      * @param zygoteArgs Additional arguments to supply to the zygote process.
      *
      * @return An object that describes the result of the attempt to start the process.
@@ -196,11 +201,12 @@
                                                   String abi,
                                                   String instructionSet,
                                                   String appDataDir,
+                                                  String invokeWith,
                                                   String[] zygoteArgs) {
         try {
             return startViaZygote(processClass, niceName, uid, gid, gids,
                     debugFlags, mountExternal, targetSdkVersion, seInfo,
-                    abi, instructionSet, appDataDir, zygoteArgs);
+                    abi, instructionSet, appDataDir, invokeWith, zygoteArgs);
         } catch (ZygoteStartFailedEx ex) {
             Log.e(LOG_TAG,
                     "Starting VM process through Zygote failed");
@@ -330,6 +336,7 @@
                                                       String abi,
                                                       String instructionSet,
                                                       String appDataDir,
+                                                      String invokeWith,
                                                       String[] extraArgs)
                                                       throws ZygoteStartFailedEx {
         ArrayList<String> argsForZygote = new ArrayList<String>();
@@ -407,6 +414,11 @@
             argsForZygote.add("--app-data-dir=" + appDataDir);
         }
 
+        if (invokeWith != null) {
+            argsForZygote.add("--invoke-with");
+            argsForZygote.add(invokeWith);
+        }
+
         argsForZygote.add(processClass);
 
         if (extraArgs != null) {
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index c83298b..7340cf7 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -869,10 +869,13 @@
      */
     @Override
     public View focusSearch(View focused, int direction) {
-        if (isRootNamespace()) {
+        if (isRootNamespace()
+                || isKeyboardNavigationCluster()
+                && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD)) {
             // root namespace means we should consider ourselves the top of the
             // tree for focus searching; otherwise we could be focus searching
-            // into other tabs.  see LocalActivityManager and TabHost for more info
+            // into other tabs.  see LocalActivityManager and TabHost for more info.
+            // Cluster's root works same way for the forward and backward navigation.
             return FocusFinder.getInstance().findNextFocus(this, focused, direction);
         } else if (mParent != null) {
             return mParent.focusSearch(focused, direction);
@@ -1104,6 +1107,12 @@
 
     @Override
     public void addFocusables(ArrayList<View> views, int direction, int focusableMode) {
+        if (isKeyboardNavigationCluster()
+                && (direction == FOCUS_FORWARD || direction == FOCUS_BACKWARD) && !hasFocus()) {
+            // A cluster cannot be focus-entered from outside using forward/backward navigation.
+            return;
+        }
+
         final int focusableCount = views.size();
 
         final int descendantFocusability = getDescendantFocusability();
@@ -3026,7 +3035,8 @@
         final View[] children = mChildren;
         for (int i = index; i != end; i += increment) {
             View child = children[i];
-            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
+                    && !child.isKeyboardNavigationCluster()) {
                 if (child.requestFocus(direction, previouslyFocusedRect)) {
                     return true;
                 }
diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java
index b9224f3..444ebc5 100644
--- a/core/java/android/widget/Chronometer.java
+++ b/core/java/android/widget/Chronometer.java
@@ -17,8 +17,11 @@
 package android.widget;
 
 import android.content.Context;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.icu.text.MeasureFormat;
+import android.icu.text.MeasureFormat.FormatWidth;
+import android.icu.util.Measure;
+import android.icu.util.MeasureUnit;
 import android.os.SystemClock;
 import android.text.format.DateUtils;
 import android.util.AttributeSet;
@@ -28,6 +31,7 @@
 
 import com.android.internal.R;
 
+import java.util.ArrayList;
 import java.util.Formatter;
 import java.util.IllegalFormatException;
 import java.util.Locale;
@@ -329,9 +333,6 @@
     private static final int MIN_IN_SEC = 60;
     private static final int HOUR_IN_SEC = MIN_IN_SEC*60;
     private static String formatDuration(long ms) {
-        final Resources res = Resources.getSystem();
-        final StringBuilder text = new StringBuilder();
-
         int duration = (int) (ms / DateUtils.SECOND_IN_MILLIS);
         if (duration < 0) {
             duration = -duration;
@@ -348,31 +349,19 @@
             m = duration / MIN_IN_SEC;
             duration -= m * MIN_IN_SEC;
         }
-        int s = duration;
+        final int s = duration;
 
-        try {
-            if (h > 0) {
-                text.append(res.getQuantityString(
-                        com.android.internal.R.plurals.duration_hours, h, h));
-            }
-            if (m > 0) {
-                if (text.length() > 0) {
-                    text.append(' ');
-                }
-                text.append(res.getQuantityString(
-                        com.android.internal.R.plurals.duration_minutes, m, m));
-            }
-
-            if (text.length() > 0) {
-                text.append(' ');
-            }
-            text.append(res.getQuantityString(
-                    com.android.internal.R.plurals.duration_seconds, s, s));
-        } catch (Resources.NotFoundException e) {
-            // Ignore; plurals throws an exception for an untranslated quantity for a given locale.
-            return null;
+        final ArrayList<Measure> measures = new ArrayList<Measure>();
+        if (h > 0) {
+            measures.add(new Measure(h, MeasureUnit.HOUR));
         }
-        return text.toString();
+        if (m > 0) {
+            measures.add(new Measure(m, MeasureUnit.MINUTE));
+        }
+        measures.add(new Measure(s, MeasureUnit.SECOND));
+
+        return MeasureFormat.getInstance(Locale.getDefault(), FormatWidth.WIDE)
+                    .formatMeasures((Measure[]) measures.toArray());
     }
 
     @Override
diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 98d87d3..eec3cb0 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/com/android/internal/content/PackageHelper.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.content;
 
-import static android.net.TrafficStats.MB_IN_BYTES;
-
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -38,7 +36,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
-import libcore.io.IoUtils;
+import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -50,6 +48,11 @@
 import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 
+import libcore.io.IoUtils;
+
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
+
 /**
  * Constants used internally between the PackageManager
  * and media container service transports.
@@ -74,6 +77,8 @@
     public static final int APP_INSTALL_INTERNAL = 1;
     public static final int APP_INSTALL_EXTERNAL = 2;
 
+    private static TestableInterface sDefaultTestableInterface = null;
+
     public static IStorageManager getStorageManager() throws RemoteException {
         IBinder service = ServiceManager.getService("mount");
         if (service != null) {
@@ -338,6 +343,65 @@
     }
 
     /**
+     * A group of external dependencies used in
+     * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values
+     * from the system or mocked ones for testing purposes.
+     */
+    public static abstract class TestableInterface {
+        abstract public StorageManager getStorageManager(Context context);
+        abstract public boolean getForceAllowOnExternalSetting(Context context);
+        abstract public boolean getAllow3rdPartyOnInternalConfig(Context context);
+        abstract public ApplicationInfo getExistingAppInfo(Context context, String packageName);
+        abstract public File getDataDirectory();
+
+        public boolean fitsOnInternalStorage(Context context, long sizeBytes) {
+            StorageManager storage = getStorageManager(context);
+            File target = getDataDirectory();
+            return (sizeBytes <= storage.getStorageBytesUntilLow(target));
+        }
+    }
+
+    private synchronized static TestableInterface getDefaultTestableInterface() {
+        if (sDefaultTestableInterface == null) {
+            sDefaultTestableInterface = new TestableInterface() {
+                @Override
+                public StorageManager getStorageManager(Context context) {
+                    return context.getSystemService(StorageManager.class);
+                }
+
+                @Override
+                public boolean getForceAllowOnExternalSetting(Context context) {
+                    return Settings.Global.getInt(context.getContentResolver(),
+                            Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
+                }
+
+                @Override
+                public boolean getAllow3rdPartyOnInternalConfig(Context context) {
+                    return context.getResources().getBoolean(
+                            com.android.internal.R.bool.config_allow3rdPartyAppOnInternal);
+                }
+
+                @Override
+                public ApplicationInfo getExistingAppInfo(Context context, String packageName) {
+                    ApplicationInfo existingInfo = null;
+                    try {
+                        existingInfo = context.getPackageManager().getApplicationInfo(packageName,
+                                PackageManager.MATCH_ANY_USER);
+                    } catch (NameNotFoundException ignored) {
+                    }
+                    return existingInfo;
+                }
+
+                @Override
+                public File getDataDirectory() {
+                    return Environment.getDataDirectory();
+                }
+            };
+        }
+        return sDefaultTestableInterface;
+    }
+
+    /**
      * Given a requested {@link PackageInfo#installLocation} and calculated
      * install size, pick the actual volume to install the app. Only considers
      * internal and private volumes, and prefers to keep an existing package on
@@ -348,25 +412,44 @@
      */
     public static String resolveInstallVolume(Context context, String packageName,
             int installLocation, long sizeBytes) throws IOException {
-        final boolean forceAllowOnExternal = Settings.Global.getInt(
-                context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
+        TestableInterface testableInterface = getDefaultTestableInterface();
+        return resolveInstallVolume(context, packageName,
+                installLocation, sizeBytes, testableInterface);
+    }
+
+    @VisibleForTesting
+    public static String resolveInstallVolume(Context context, String packageName,
+            int installLocation, long sizeBytes, TestableInterface testInterface)
+            throws IOException {
+        final boolean forceAllowOnExternal = testInterface.getForceAllowOnExternalSetting(context);
+        final boolean allow3rdPartyOnInternal =
+                testInterface.getAllow3rdPartyOnInternalConfig(context);
         // TODO: handle existing apps installed in ASEC; currently assumes
         // they'll end up back on internal storage
-        ApplicationInfo existingInfo = null;
-        try {
-            existingInfo = context.getPackageManager().getApplicationInfo(packageName,
-                    PackageManager.MATCH_ANY_USER);
-        } catch (NameNotFoundException ignored) {
+        ApplicationInfo existingInfo = testInterface.getExistingAppInfo(context, packageName);
+
+        final boolean fitsOnInternal = testInterface.fitsOnInternalStorage(context, sizeBytes);
+        final StorageManager storageManager =
+                testInterface.getStorageManager(context);
+
+        // System apps always forced to internal storage
+        if (existingInfo != null && existingInfo.isSystemApp()) {
+            if (fitsOnInternal) {
+                return StorageManager.UUID_PRIVATE_INTERNAL;
+            } else {
+                throw new IOException("Not enough space on existing volume "
+                        + existingInfo.volumeUuid + " for system app " + packageName + " upgrade");
+            }
         }
 
-        final StorageManager storageManager = context.getSystemService(StorageManager.class);
-        final boolean fitsOnInternal = fitsOnInternal(context, sizeBytes);
-
+        // Now deal with non-system apps.
         final ArraySet<String> allCandidates = new ArraySet<>();
         VolumeInfo bestCandidate = null;
         long bestCandidateAvailBytes = Long.MIN_VALUE;
         for (VolumeInfo vol : storageManager.getVolumes()) {
-            if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()) {
+            boolean isInternalStorage = ID_PRIVATE_INTERNAL.equals(vol.id);
+            if (vol.type == VolumeInfo.TYPE_PRIVATE && vol.isMountedWritable()
+                    && (!isInternalStorage || allow3rdPartyOnInternal)) {
                 final long availBytes = storageManager.getStorageBytesUntilLow(new File(vol.path));
                 if (availBytes >= sizeBytes) {
                     allCandidates.add(vol.fsUuid);
@@ -378,11 +461,6 @@
             }
         }
 
-        // System apps always forced to internal storage
-        if (existingInfo != null && existingInfo.isSystemApp()) {
-            installLocation = PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY;
-        }
-
         // If app expresses strong desire for internal storage, honor it
         if (!forceAllowOnExternal
                 && installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY) {
@@ -391,6 +469,11 @@
                 throw new IOException("Cannot automatically move " + packageName + " from "
                         + existingInfo.volumeUuid + " to internal storage");
             }
+
+            if (!allow3rdPartyOnInternal) {
+                throw new IOException("Not allowed to install non-system apps on internal storage");
+            }
+
             if (fitsOnInternal) {
                 return StorageManager.UUID_PRIVATE_INTERNAL;
             } else {
@@ -411,14 +494,13 @@
             }
         }
 
-        // We're left with either preferring external or auto, so just pick
+        // We're left with new installations with either preferring external or auto, so just pick
         // volume with most space
         if (bestCandidate != null) {
             return bestCandidate.fsUuid;
-        } else if (fitsOnInternal) {
-            return StorageManager.UUID_PRIVATE_INTERNAL;
         } else {
-            throw new IOException("No special requests, but no room anywhere");
+            throw new IOException("No special requests, but no room on allowed volumes. "
+                + " allow3rdPartyOnInternal? " + allow3rdPartyOnInternal);
         }
     }
 
diff --git a/core/java/com/android/internal/os/IDropBoxManagerService.aidl b/core/java/com/android/internal/os/IDropBoxManagerService.aidl
index d067926..d16579c 100644
--- a/core/java/com/android/internal/os/IDropBoxManagerService.aidl
+++ b/core/java/com/android/internal/os/IDropBoxManagerService.aidl
@@ -17,7 +17,6 @@
 package com.android.internal.os;
 
 import android.os.DropBoxManager;
-import android.os.ParcelFileDescriptor;
 
 /**
  * "Backend" interface used by {@link android.os.DropBoxManager} to talk to the
diff --git a/core/java/com/android/internal/os/WebViewZygoteInit.java b/core/java/com/android/internal/os/WebViewZygoteInit.java
index d968e3c..a8a5549 100644
--- a/core/java/com/android/internal/os/WebViewZygoteInit.java
+++ b/core/java/com/android/internal/os/WebViewZygoteInit.java
@@ -62,6 +62,9 @@
             ClassLoader loader = ApplicationLoaders.getDefault().createAndCacheWebViewClassLoader(
                     packagePath, libsPath);
 
+            // Add the APK to the Zygote's list of allowed files for children.
+            Zygote.nativeAllowFileAcrossFork(packagePath);
+
             // Once we have the classloader, look up the WebViewFactoryProvider implementation and
             // call preloadInZygote() on it to give it the opportunity to preload the native library
             // and perform any other initialisation work that should be shared among the children.
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index fc0ccb7..293de3d 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -153,6 +153,11 @@
             int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);
 
     /**
+     * Lets children of the zygote inherit open file descriptors to this path.
+     */
+    native protected static void nativeAllowFileAcrossFork(String path);
+
+    /**
      * Zygote unmount storage space on initializing.
      * This method is called once.
      */
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 7edc938..39cb464 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -695,9 +695,11 @@
             throws ZygoteSecurityException {
         int peerUid = peer.getUid();
 
-        if (args.invokeWith != null && peerUid != 0) {
-            throw new ZygoteSecurityException("Peer is not permitted to specify "
-                    + "an explicit invoke-with wrapper command");
+        if (args.invokeWith != null && peerUid != 0 &&
+            (args.debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) == 0) {
+            throw new ZygoteSecurityException("Peer is permitted to specify an"
+                    + "explicit invoke-with wrapper command only for debuggable"
+                    + "applications.");
         }
     }
 
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 35c9ee3..3555057 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -188,6 +188,7 @@
     com_android_internal_util_VirtualRefBasePtr.cpp \
     com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp \
     hwbinder/EphemeralStorage.cpp \
+    fd_utils.cpp \
 
 LOCAL_C_INCLUDES += \
     $(LOCAL_PATH)/include \
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index 673cf86..80711aa 100755
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -5,7 +5,11 @@
 #include "SkPixelRef.h"
 #include "SkImageEncoder.h"
 #include "SkImageInfo.h"
+#include "SkColor.h"
 #include "SkColorPriv.h"
+#include "SkHalf.h"
+#include "SkPM4f.h"
+#include "SkPM4fPriv.h"
 #include "GraphicsJNI.h"
 #include "SkDither.h"
 #include "SkUnPreMultiply.h"
@@ -232,6 +236,28 @@
 typedef void (*FromColorProc)(void* dst, const SkColor src[], int width,
                               int x, int y);
 
+static void FromColor_F16(void* dst, const SkColor src[], int width,
+                          int, int) {
+    uint64_t* d = (uint64_t*)dst;
+
+    for (int i = 0; i < width; i++) {
+        *d++ = SkColor4f::FromColor(*src++).premul().toF16();
+    }
+}
+
+static void FromColor_F16_Raw(void* dst, const SkColor src[], int width,
+                          int, int) {
+    uint64_t* d = (uint64_t*)dst;
+
+    for (int i = 0; i < width; i++) {
+        const float* color = SkColor4f::FromColor(*src++).vec();
+        uint16_t* scratch = reinterpret_cast<uint16_t*>(d++);
+        for (int i = 0; i < 4; ++i) {
+            scratch[i] = SkFloatToHalf(color[i]);
+        }
+    }
+}
+
 static void FromColor_D32(void* dst, const SkColor src[], int width,
                           int, int) {
     SkPMColor* d = (SkPMColor*)dst;
@@ -321,6 +347,8 @@
             return FromColor_D565;
         case kAlpha_8_SkColorType:
             return FromColor_DA8;
+        case kRGBA_F16_SkColorType:
+            return bitmap.alphaType() == kPremul_SkAlphaType ? FromColor_F16 : FromColor_F16_Raw;
         default:
             break;
     }
@@ -351,8 +379,7 @@
 
     dstBitmap.notifyPixelsChanged();
 
-    env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array),
-                                 JNI_ABORT);
+    env->ReleaseIntArrayElements(srcColors, const_cast<jint*>(array), JNI_ABORT);
     return true;
 }
 
@@ -361,6 +388,24 @@
 typedef void (*ToColorProc)(SkColor dst[], const void* src, int width,
                             SkColorTable*);
 
+static void ToColor_F16_Alpha(SkColor dst[], const void* src, int width,
+                              SkColorTable*) {
+    SkASSERT(width > 0);
+    uint64_t* s = (uint64_t*)src;
+    do {
+        *dst++ = SkPM4f::FromF16((const uint16_t*) s++).unpremul().toSkColor();
+    } while (--width != 0);
+}
+
+static void ToColor_F16_Raw(SkColor dst[], const void* src, int width,
+                            SkColorTable*) {
+    SkASSERT(width > 0);
+    uint64_t* s = (uint64_t*)src;
+    do {
+        *dst++ = Sk4f_toS32(swizzle_rb(SkHalfToFloat_finite_ftz(*s++)));
+    } while (--width != 0);
+}
+
 static void ToColor_S32_Alpha(SkColor dst[], const void* src, int width,
                               SkColorTable*) {
     SkASSERT(width > 0);
@@ -520,6 +565,17 @@
             }
         case kAlpha_8_SkColorType:
             return ToColor_SA8;
+        case kRGBA_F16_SkColorType:
+            switch (src.alphaType()) {
+                case kOpaque_SkAlphaType:
+                    return ToColor_F16_Raw;
+                case kPremul_SkAlphaType:
+                    return ToColor_F16_Alpha;
+                case kUnpremul_SkAlphaType:
+                    return ToColor_F16_Raw;
+                default:
+                    return NULL;
+            }
         default:
             break;
     }
@@ -554,7 +610,7 @@
 
     SkBitmap bitmap;
     bitmap.setInfo(SkImageInfo::Make(width, height, colorType, kPremul_SkAlphaType,
-            GraphicsJNI::defaultColorSpace()));
+            GraphicsJNI::colorSpaceForType(colorType)));
 
     sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bitmap, NULL);
     if (!nativeBitmap) {
@@ -562,8 +618,7 @@
     }
 
     if (jColors != NULL) {
-        GraphicsJNI::SetPixels(env, jColors, offset, stride,
-                0, 0, width, height, bitmap);
+        GraphicsJNI::SetPixels(env, jColors, offset, stride, 0, 0, width, height, bitmap);
     }
 
     return createBitmap(env, nativeBitmap.release(), getPremulBitmapCreateFlags(isMutable));
@@ -790,6 +845,7 @@
     const int         density = p->readInt32();
 
     if (kN32_SkColorType != colorType &&
+            kRGBA_F16_SkColorType != colorType &&
             kRGB_565_SkColorType != colorType &&
             kARGB_4444_SkColorType != colorType &&
             kIndex_8_SkColorType != colorType &&
@@ -800,8 +856,15 @@
 
     std::unique_ptr<SkBitmap> bitmap(new SkBitmap);
 
-    if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType,
-            isSRGB ? SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named) : nullptr), rowBytes)) {
+    sk_sp<SkColorSpace> colorSpace;
+    if (kRGBA_F16_SkColorType == colorType) {
+        colorSpace = SkColorSpace::MakeNamed(SkColorSpace::kSRGBLinear_Named);
+    } else {
+        colorSpace = isSRGB ? SkColorSpace::MakeNamed(SkColorSpace::kSRGB_Named) : nullptr;
+    }
+
+    if (!bitmap->setInfo(SkImageInfo::Make(width, height, colorType, alphaType, colorSpace),
+            rowBytes)) {
         return NULL;
     }
 
@@ -1127,8 +1190,7 @@
     }
 }
 
-static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle,
-                              jlong bm1Handle) {
+static jboolean Bitmap_sameAs(JNIEnv* env, jobject, jlong bm0Handle, jlong bm1Handle) {
     SkBitmap bm0;
     SkBitmap bm1;
 
@@ -1144,11 +1206,11 @@
 
     bitmap0->bitmap().getSkBitmap(&bm0);
     bitmap1->bitmap().getSkBitmap(&bm1);
-    if (bm0.width() != bm1.width() ||
-        bm0.height() != bm1.height() ||
-        bm0.colorType() != bm1.colorType() ||
-        bm0.alphaType() != bm1.alphaType() ||
-        bm0.colorSpace() != bm1.colorSpace()) {
+    if (bm0.width() != bm1.width()
+            || bm0.height() != bm1.height()
+            || bm0.colorType() != bm1.colorType()
+            || bm0.alphaType() != bm1.alphaType()
+            || !SkColorSpace::Equals(bm0.colorSpace(), bm1.colorSpace())) {
         return JNI_FALSE;
     }
 
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 18ed0ed..69c7054 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -283,8 +283,8 @@
 
     // Create the codec.
     NinePatchPeeker peeker;
-    std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(streamDeleter.release(),
-            &peeker));
+    std::unique_ptr<SkAndroidCodec> codec(SkAndroidCodec::NewFromStream(
+            streamDeleter.release(), &peeker));
     if (!codec.get()) {
         return nullObjectReturn("SkAndroidCodec::NewFromStream returned null");
     }
@@ -399,7 +399,8 @@
 
     // We always decode to sRGB, but only mark the bitmap with a color space if linear
     // blending is enabled.
-    SkImageInfo bitmapInfo = decodeInfo.makeColorSpace(GraphicsJNI::defaultColorSpace());
+    SkImageInfo bitmapInfo = decodeInfo.makeColorSpace(
+            GraphicsJNI::colorSpaceForType(decodeColorType));
     if (decodeColorType == kGray_8_SkColorType) {
         // The legacy implementation of BitmapFactory used kAlpha8 for
         // grayscale images (before kGray8 existed).  While the codec
diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp
index d8984d3..6f97c60 100644
--- a/core/jni/android/graphics/Graphics.cpp
+++ b/core/jni/android/graphics/Graphics.cpp
@@ -297,13 +297,16 @@
     kRGB_565_LegacyBitmapConfig     = 3,
     kARGB_4444_LegacyBitmapConfig   = 4,
     kARGB_8888_LegacyBitmapConfig   = 5,
-    kHardware_LegacyBitmapConfig    = 6,
+    kRGBA_16F_LegacyBitmapConfig    = 6,
+    kHardware_LegacyBitmapConfig    = 7,
 
     kLastEnum_LegacyBitmapConfig = kHardware_LegacyBitmapConfig
 };
 
 jint GraphicsJNI::colorTypeToLegacyBitmapConfig(SkColorType colorType) {
     switch (colorType) {
+        case kRGBA_F16_SkColorType:
+            return kRGBA_16F_LegacyBitmapConfig;
         case kN32_SkColorType:
             return kARGB_8888_LegacyBitmapConfig;
         case kARGB_4444_SkColorType:
@@ -329,6 +332,7 @@
         kRGB_565_SkColorType,
         kARGB_4444_SkColorType,
         kN32_SkColorType,
+        kRGBA_F16_SkColorType,
         kN32_SkColorType
     };
 
@@ -458,6 +462,19 @@
 #endif
 }
 
+sk_sp<SkColorSpace> GraphicsJNI::linearColorSpace() {
+    return SkColorSpace::MakeNamed(SkColorSpace::kSRGBLinear_Named);
+}
+
+sk_sp<SkColorSpace> GraphicsJNI::colorSpaceForType(SkColorType type) {
+    switch (type) {
+        case kRGBA_F16_SkColorType:
+            return linearColorSpace();
+        default:
+            return defaultColorSpace();
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 bool HeapAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) {
     mStorage = android::Bitmap::allocateHeapBitmap(bitmap, ctable);
diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h
index 03dc1fb..508c9ff 100644
--- a/core/jni/android/graphics/GraphicsJNI.h
+++ b/core/jni/android/graphics/GraphicsJNI.h
@@ -95,6 +95,8 @@
             const SkBitmap& dstBitmap);
 
     static sk_sp<SkColorSpace> defaultColorSpace();
+    static sk_sp<SkColorSpace> linearColorSpace();
+    static sk_sp<SkColorSpace> colorSpaceForType(SkColorType type);
 };
 
 class HeapAllocator : public SkBRDAllocator {
diff --git a/core/jni/android_view_GraphicBuffer.cpp b/core/jni/android_view_GraphicBuffer.cpp
index b6b5245..f18837f 100644
--- a/core/jni/android_view_GraphicBuffer.cpp
+++ b/core/jni/android_view_GraphicBuffer.cpp
@@ -142,6 +142,8 @@
             return kN32_SkColorType;
         case PIXEL_FORMAT_RGBX_8888:
             return kN32_SkColorType;
+        case PIXEL_FORMAT_RGBA_FP16:
+            return kRGBA_F16_SkColorType;
         case PIXEL_FORMAT_RGB_565:
             return kRGB_565_SkColorType;
         default:
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 92693b7..96e6f81 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -168,6 +168,7 @@
     switch(format) {
         case HAL_PIXEL_FORMAT_RGBA_8888:
         case HAL_PIXEL_FORMAT_RGBX_8888:
+        case HAL_PIXEL_FORMAT_RGBA_FP16:
         case HAL_PIXEL_FORMAT_RGB_888:
         case HAL_PIXEL_FORMAT_RGB_565:
         case HAL_PIXEL_FORMAT_Y8:
@@ -283,6 +284,7 @@
     switch (format) {
     case PIXEL_FORMAT_RGBX_8888:    return kN32_SkColorType;
     case PIXEL_FORMAT_RGBA_8888:    return kN32_SkColorType;
+    case PIXEL_FORMAT_RGBA_FP16:    return kRGBA_F16_SkColorType;
     case PIXEL_FORMAT_RGB_565:      return kRGB_565_SkColorType;
     default:                        return kUnknown_SkColorType;
     }
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 5b88181..ed071cd 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -164,6 +164,11 @@
             alphaType = kPremul_SkAlphaType;
             break;
         }
+        case PIXEL_FORMAT_RGBA_FP16: {
+            colorType = kRGBA_F16_SkColorType;
+            alphaType = kPremul_SkAlphaType;
+            break;
+        }
         case PIXEL_FORMAT_RGB_565: {
             colorType = kRGB_565_SkColorType;
             alphaType = kOpaque_SkAlphaType;
diff --git a/core/jni/android_view_TextureView.cpp b/core/jni/android_view_TextureView.cpp
index 3c8db7f..613e040 100644
--- a/core/jni/android_view_TextureView.cpp
+++ b/core/jni/android_view_TextureView.cpp
@@ -83,6 +83,10 @@
             colorType = kN32_SkColorType;
             alphaType = kOpaque_SkAlphaType;
             break;
+        case WINDOW_FORMAT_RGBA_FP16:
+            colorType = kRGBA_F16_SkColorType;
+            alphaType = kPremul_SkAlphaType;
+            break;
         case WINDOW_FORMAT_RGB_565:
             colorType = kRGB_565_SkColorType;
             alphaType = kOpaque_SkAlphaType;
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 850c9c6..f8f9efe 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -243,7 +243,7 @@
         return INSTALL_FAILED_INTERNAL_ERROR;
     }
 
-    *(localFileName + nativeLibPath.size()) = '/';
+    *(localTmpFileName + nativeLibPath.size()) = '/';
 
     if (strlcpy(localTmpFileName + nativeLibPath.size(), TMP_FILE_PATTERN,
                     TMP_FILE_PATTERN_LEN - nativeLibPath.size()) != TMP_FILE_PATTERN_LEN) {
@@ -344,6 +344,11 @@
             const char* lastSlash = strrchr(fileName, '/');
             ALOG_ASSERT(lastSlash != NULL, "last slash was null somehow for %s\n", fileName);
 
+            // Skip directories.
+            if (*(lastSlash + 1) == 0) {
+                continue;
+            }
+
             // Make sure the filename is safe.
             if (!isFilenameSafe(lastSlash + 1)) {
                 continue;
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index da059e3..cc7b958 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -56,7 +56,7 @@
 #include "ScopedLocalRef.h"
 #include "ScopedPrimitiveArray.h"
 #include "ScopedUtfChars.h"
-#include "fd_utils-inl.h"
+#include "fd_utils.h"
 
 #include "nativebridge/native_bridge.h"
 
@@ -709,6 +709,16 @@
   return pid;
 }
 
+static void com_android_internal_os_Zygote_nativeAllowFileAcrossFork(
+        JNIEnv* env, jclass, jstring path) {
+    ScopedUtfChars path_native(env, path);
+    const char* path_cstr = path_native.c_str();
+    if (!path_cstr) {
+        RuntimeAbort(env, __LINE__, "path_cstr == NULL");
+    }
+    FileDescriptorWhitelist::Get()->Allow(path_cstr);
+}
+
 static void com_android_internal_os_Zygote_nativeUnmountStorageOnInit(JNIEnv* env, jclass) {
     // Zygote process unmount root storage space initially before every child processes are forked.
     // Every forked child processes (include SystemServer) only mount their own root storage space
@@ -753,6 +763,8 @@
       (void *) com_android_internal_os_Zygote_nativeForkAndSpecialize },
     { "nativeForkSystemServer", "(II[II[[IJJ)I",
       (void *) com_android_internal_os_Zygote_nativeForkSystemServer },
+    { "nativeAllowFileAcrossFork", "(Ljava/lang/String;)V",
+      (void *) com_android_internal_os_Zygote_nativeAllowFileAcrossFork },
     { "nativeUnmountStorageOnInit", "()V",
       (void *) com_android_internal_os_Zygote_nativeUnmountStorageOnInit }
 };
diff --git a/core/jni/fd_utils-inl.h b/core/jni/fd_utils-inl.h
deleted file mode 100644
index e270911..0000000
--- a/core/jni/fd_utils-inl.h
+++ /dev/null
@@ -1,587 +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.
- */
-
-#include <string>
-#include <unordered_map>
-#include <set>
-#include <vector>
-#include <algorithm>
-
-#include <android-base/strings.h>
-#include <dirent.h>
-#include <fcntl.h>
-#include <grp.h>
-#include <inttypes.h>
-#include <stdlib.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/un.h>
-#include <unistd.h>
-
-#include <cutils/log.h>
-#include "JNIHelp.h"
-#include "ScopedPrimitiveArray.h"
-
-// Whitelist of open paths that the zygote is allowed to keep open.
-//
-// In addition to the paths listed here, all files ending with
-// ".jar" under /system/framework" are whitelisted. See
-// FileDescriptorInfo::IsWhitelisted for the canonical definition.
-//
-// If the whitelisted path is associated with a regular file or a
-// character device, the file is reopened after a fork with the same
-// offset and mode. If the whilelisted  path is associated with a
-// AF_UNIX socket, the socket will refer to /dev/null after each
-// fork, and all operations on it will fail.
-static const char* kPathWhitelist[] = {
-  "/dev/null",
-  "/dev/socket/zygote",
-  "/dev/socket/zygote_secondary",
-  "/dev/socket/webview_zygote",
-  "/sys/kernel/debug/tracing/trace_marker",
-  "/system/framework/framework-res.apk",
-  "/dev/urandom",
-  "/dev/ion",
-  "/dev/dri/renderD129", // Fixes b/31172436
-};
-
-static const char* kFdPath = "/proc/self/fd";
-
-// Keeps track of all relevant information (flags, offset etc.) of an
-// open zygote file descriptor.
-class FileDescriptorInfo {
- public:
-  // Create a FileDescriptorInfo for a given file descriptor. Returns
-  // |NULL| if an error occurred.
-  static FileDescriptorInfo* createFromFd(int fd) {
-    struct stat f_stat;
-    // This should never happen; the zygote should always have the right set
-    // of permissions required to stat all its open files.
-    if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
-      ALOGE("Unable to stat fd %d : %s", fd, strerror(errno));
-      return NULL;
-    }
-
-    if (S_ISSOCK(f_stat.st_mode)) {
-      std::string socket_name;
-      if (!GetSocketName(fd, &socket_name)) {
-        return NULL;
-      }
-
-      if (!IsWhitelisted(socket_name)) {
-        ALOGE("Socket name not whitelisted : %s (fd=%d)", socket_name.c_str(), fd);
-        return NULL;
-      }
-
-      return new FileDescriptorInfo(fd);
-    }
-
-    // We only handle whitelisted regular files and character devices. Whitelisted
-    // character devices must provide a guarantee of sensible behaviour when
-    // reopened.
-    //
-    // S_ISDIR : Not supported. (We could if we wanted to, but it's unused).
-    // S_ISLINK : Not supported.
-    // S_ISBLK : Not supported.
-    // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate
-    // with the child process across forks but those should have been closed
-    // before we got to this point.
-    if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) {
-      ALOGE("Unsupported st_mode %d", f_stat.st_mode);
-      return NULL;
-    }
-
-    std::string file_path;
-    if (!Readlink(fd, &file_path)) {
-      return NULL;
-    }
-
-    if (!IsWhitelisted(file_path)) {
-      ALOGE("Not whitelisted : %s", file_path.c_str());
-      return NULL;
-    }
-
-    // File descriptor flags : currently on FD_CLOEXEC. We can set these
-    // using F_SETFD - we're single threaded at this point of execution so
-    // there won't be any races.
-    const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD));
-    if (fd_flags == -1) {
-      ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno));
-      return NULL;
-    }
-
-    // File status flags :
-    // - File access mode : (O_RDONLY, O_WRONLY...) we'll pass these through
-    //   to the open() call.
-    //
-    // - File creation flags : (O_CREAT, O_EXCL...) - there's not much we can
-    //   do about these, since the file has already been created. We shall ignore
-    //   them here.
-    //
-    // - Other flags : We'll have to set these via F_SETFL. On linux, F_SETFL
-    //   can only set O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK.
-    //   In particular, it can't set O_SYNC and O_DSYNC. We'll have to test for
-    //   their presence and pass them in to open().
-    int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
-    if (fs_flags == -1) {
-      ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno));
-      return NULL;
-    }
-
-    // File offset : Ignore the offset for non seekable files.
-    const off_t offset = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR));
-
-    // We pass the flags that open accepts to open, and use F_SETFL for
-    // the rest of them.
-    static const int kOpenFlags = (O_RDONLY | O_WRONLY | O_RDWR | O_DSYNC | O_SYNC);
-    int open_flags = fs_flags & (kOpenFlags);
-    fs_flags = fs_flags & (~(kOpenFlags));
-
-    return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
-  }
-
-  // Checks whether the file descriptor associated with this object
-  // refers to the same description.
-  bool Restat() const {
-    struct stat f_stat;
-    if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
-      return false;
-    }
-
-    return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev;
-  }
-
-  bool ReopenOrDetach() const {
-    if (is_sock) {
-      return DetachSocket();
-    }
-
-    // NOTE: This might happen if the file was unlinked after being opened.
-    // It's a common pattern in the case of temporary files and the like but
-    // we should not allow such usage from the zygote.
-    const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags));
-
-    if (new_fd == -1) {
-      ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno));
-      return false;
-    }
-
-    if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) {
-      close(new_fd);
-      ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno));
-      return false;
-    }
-
-    if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) {
-      close(new_fd);
-      ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno));
-      return false;
-    }
-
-    if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) {
-      close(new_fd);
-      ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno));
-      return false;
-    }
-
-    if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) {
-      close(new_fd);
-      ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno));
-      return false;
-    }
-
-    close(new_fd);
-
-    return true;
-  }
-
-  const int fd;
-  const struct stat stat;
-  const std::string file_path;
-  const int open_flags;
-  const int fd_flags;
-  const int fs_flags;
-  const off_t offset;
-  const bool is_sock;
-
- private:
-  FileDescriptorInfo(int fd) :
-    fd(fd),
-    stat(),
-    open_flags(0),
-    fd_flags(0),
-    fs_flags(0),
-    offset(0),
-    is_sock(true) {
-  }
-
-  FileDescriptorInfo(struct stat stat, const std::string& file_path, int fd, int open_flags,
-                     int fd_flags, int fs_flags, off_t offset) :
-    fd(fd),
-    stat(stat),
-    file_path(file_path),
-    open_flags(open_flags),
-    fd_flags(fd_flags),
-    fs_flags(fs_flags),
-    offset(offset),
-    is_sock(false) {
-  }
-
-  static bool StartsWith(const std::string& str, const std::string& prefix) {
-    return str.compare(0, prefix.size(), prefix) == 0;
-  }
-
-  static bool EndsWith(const std::string& str, const std::string& suffix) {
-    if (suffix.size() > str.size()) {
-      return false;
-    }
-
-    return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
-  }
-
-  // Returns true iff. a given path is whitelisted. A path is whitelisted
-  // if it belongs to the whitelist (see kPathWhitelist) or if it's a path
-  // under /system/framework that ends with ".jar" or if it is a system
-  // framework overlay.
-  static bool IsWhitelisted(const std::string& path) {
-    for (size_t i = 0; i < (sizeof(kPathWhitelist) / sizeof(kPathWhitelist[0])); ++i) {
-      if (kPathWhitelist[i] == path) {
-        return true;
-      }
-    }
-
-    static const std::string kFrameworksPrefix = "/system/framework/";
-    static const std::string kJarSuffix = ".jar";
-    if (StartsWith(path, kFrameworksPrefix) && EndsWith(path, kJarSuffix)) {
-      return true;
-    }
-
-    // Whitelist files needed for Runtime Resource Overlay, like these:
-    // /system/vendor/overlay/framework-res.apk
-    // /system/vendor/overlay-subdir/pg/framework-res.apk
-    // /vendor/overlay/framework-res.apk
-    // /vendor/overlay/PG/android-framework-runtime-resource-overlay.apk
-    // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap
-    // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap
-    // See AssetManager.cpp for more details on overlay-subdir.
-    static const std::string kOverlayDir = "/system/vendor/overlay/";
-    static const std::string kVendorOverlayDir = "/vendor/overlay";
-    static const std::string kOverlaySubdir = "/system/vendor/overlay-subdir/";
-    static const std::string kApkSuffix = ".apk";
-
-    if ((StartsWith(path, kOverlayDir) || StartsWith(path, kOverlaySubdir)
-         || StartsWith(path, kVendorOverlayDir))
-        && EndsWith(path, kApkSuffix)
-        && path.find("/../") == std::string::npos) {
-      return true;
-    }
-
-    static const std::string kOverlayIdmapPrefix = "/data/resource-cache/";
-    static const std::string kOverlayIdmapSuffix = ".apk@idmap";
-    if (StartsWith(path, kOverlayIdmapPrefix) && EndsWith(path, kOverlayIdmapSuffix)
-        && path.find("/../") == std::string::npos) {
-      return true;
-    }
-
-    // All regular files that are placed under this path are whitelisted automatically.
-    static const std::string kZygoteWhitelistPath = "/vendor/zygote_whitelist/";
-    if (StartsWith(path, kZygoteWhitelistPath) && path.find("/../") == std::string::npos) {
-      return true;
-    }
-
-    return false;
-  }
-
-  // TODO: Call android::base::Readlink instead of copying the code here.
-  static bool Readlink(const int fd, std::string* result) {
-    char path[64];
-    snprintf(path, sizeof(path), "/proc/self/fd/%d", fd);
-
-    // Code copied from android::base::Readlink starts here :
-
-    // Annoyingly, the readlink system call returns EINVAL for a zero-sized buffer,
-    // and truncates to whatever size you do supply, so it can't be used to query.
-    // We could call lstat first, but that would introduce a race condition that
-    // we couldn't detect.
-    // ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here.
-    char buf[4096];
-    ssize_t len = readlink(path, buf, sizeof(buf));
-    if (len == -1) return false;
-
-    result->assign(buf, len);
-    return true;
-  }
-
-  // Returns the locally-bound name of the socket |fd|. Returns true
-  // iff. all of the following hold :
-  //
-  // - the socket's sa_family is AF_UNIX.
-  // - the length of the path is greater than zero (i.e, not an unnamed socket).
-  // - the first byte of the path isn't zero (i.e, not a socket with an abstract
-  //   address).
-  static bool GetSocketName(const int fd, std::string* result) {
-    sockaddr_storage ss;
-    sockaddr* addr = reinterpret_cast<sockaddr*>(&ss);
-    socklen_t addr_len = sizeof(ss);
-
-    if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) {
-      ALOGE("Failed getsockname(%d) : %s", fd, strerror(errno));
-      return false;
-    }
-
-    if (addr->sa_family != AF_UNIX) {
-      ALOGE("Unsupported socket (fd=%d) with family %d", fd, addr->sa_family);
-      return false;
-    }
-
-    const sockaddr_un* unix_addr = reinterpret_cast<const sockaddr_un*>(&ss);
-
-    size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path);
-    // This is an unnamed local socket, we do not accept it.
-    if (path_len == 0) {
-      ALOGE("Unsupported AF_UNIX socket (fd=%d) with empty path.", fd);
-      return false;
-    }
-
-    // This is a local socket with an abstract address, we do not accept it.
-    if (unix_addr->sun_path[0] == '\0') {
-      ALOGE("Unsupported AF_UNIX socket (fd=%d) with abstract address.", fd);
-      return false;
-    }
-
-    // If we're here, sun_path must refer to a null terminated filesystem
-    // pathname (man 7 unix). Remove the terminator before assigning it to an
-    // std::string.
-    if (unix_addr->sun_path[path_len - 1] ==  '\0') {
-      --path_len;
-    }
-
-    result->assign(unix_addr->sun_path, path_len);
-    return true;
-  }
-
-  bool DetachSocket() const {
-    const int dev_null_fd = open("/dev/null", O_RDWR);
-    if (dev_null_fd < 0) {
-      ALOGE("Failed to open /dev/null : %s", strerror(errno));
-      return false;
-    }
-
-    if (dup2(dev_null_fd, fd) == -1) {
-      ALOGE("Failed dup2 on socket descriptor %d : %s", fd, strerror(errno));
-      return false;
-    }
-
-    if (close(dev_null_fd) == -1) {
-      ALOGE("Failed close(%d) : %s", dev_null_fd, strerror(errno));
-      return false;
-    }
-
-    return true;
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
-};
-
-// A FileDescriptorTable is a collection of FileDescriptorInfo objects
-// keyed by their FDs.
-class FileDescriptorTable {
- public:
-  // Creates a new FileDescriptorTable. This function scans
-  // /proc/self/fd for the list of open file descriptors and collects
-  // information about them. Returns NULL if an error occurs.
-  static FileDescriptorTable* Create() {
-    DIR* d = opendir(kFdPath);
-    if (d == NULL) {
-      ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno));
-      return NULL;
-    }
-    int dir_fd = dirfd(d);
-    dirent* e;
-
-    std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
-    while ((e = readdir(d)) != NULL) {
-      const int fd = ParseFd(e, dir_fd);
-      if (fd == -1) {
-        continue;
-      }
-
-      FileDescriptorInfo* info = FileDescriptorInfo::createFromFd(fd);
-      if (info == NULL) {
-        if (closedir(d) == -1) {
-          ALOGE("Unable to close directory : %s", strerror(errno));
-        }
-        return NULL;
-      }
-      open_fd_map[fd] = info;
-    }
-
-    if (closedir(d) == -1) {
-      ALOGE("Unable to close directory : %s", strerror(errno));
-      return NULL;
-    }
-    return new FileDescriptorTable(open_fd_map);
-  }
-
-  bool Restat() {
-    std::set<int> open_fds;
-
-    // First get the list of open descriptors.
-    DIR* d = opendir(kFdPath);
-    if (d == NULL) {
-      ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno));
-      return false;
-    }
-
-    int dir_fd = dirfd(d);
-    dirent* e;
-    while ((e = readdir(d)) != NULL) {
-      const int fd = ParseFd(e, dir_fd);
-      if (fd == -1) {
-        continue;
-      }
-
-      open_fds.insert(fd);
-    }
-
-    if (closedir(d) == -1) {
-      ALOGE("Unable to close directory : %s", strerror(errno));
-      return false;
-    }
-
-    return RestatInternal(open_fds);
-  }
-
-  // Reopens all file descriptors that are contained in the table. Returns true
-  // if all descriptors were successfully re-opened or detached, and false if an
-  // error occurred.
-  bool ReopenOrDetach() {
-    std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
-    for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
-      const FileDescriptorInfo* info = it->second;
-      if (info == NULL || !info->ReopenOrDetach()) {
-        return false;
-      }
-    }
-
-    return true;
-  }
-
- private:
-  FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map)
-      : open_fd_map_(map) {
-  }
-
-  bool RestatInternal(std::set<int>& open_fds) {
-    bool error = false;
-
-    // Iterate through the list of file descriptors we've already recorded
-    // and check whether :
-    //
-    // (a) they continue to be open.
-    // (b) they refer to the same file.
-    std::unordered_map<int, FileDescriptorInfo*>::iterator it = open_fd_map_.begin();
-    while (it != open_fd_map_.end()) {
-      std::set<int>::const_iterator element = open_fds.find(it->first);
-      if (element == open_fds.end()) {
-        // The entry from the file descriptor table is no longer in the list
-        // of open files. We warn about this condition and remove it from
-        // the list of FDs under consideration.
-        //
-        // TODO(narayan): This will be an error in a future android release.
-        // error = true;
-        // ALOGW("Zygote closed file descriptor %d.", it->first);
-        it = open_fd_map_.erase(it);
-      } else {
-        // The entry from the file descriptor table is still open. Restat
-        // it and check whether it refers to the same file.
-        const bool same_file = it->second->Restat();
-        if (!same_file) {
-          // The file descriptor refers to a different description. We must
-          // update our entry in the table.
-          delete it->second;
-          it->second = FileDescriptorInfo::createFromFd(*element);
-          if (it->second == NULL) {
-            // The descriptor no longer no longer refers to a whitelisted file.
-            // We flag an error and remove it from the list of files we're
-            // tracking.
-            error = true;
-            it = open_fd_map_.erase(it);
-          } else {
-            // Successfully restatted the file, move on to the next open FD.
-            ++it;
-          }
-        } else {
-          // It's the same file. Nothing to do here. Move on to the next open
-          // FD.
-          ++it;
-        }
-
-        // Finally, remove the FD from the set of open_fds. We do this last because
-        // |element| will not remain valid after a call to erase.
-        open_fds.erase(element);
-      }
-    }
-
-    if (open_fds.size() > 0) {
-      // The zygote has opened new file descriptors since our last inspection.
-      // We warn about this condition and add them to our table.
-      //
-      // TODO(narayan): This will be an error in a future android release.
-      // error = true;
-      // ALOGW("Zygote opened %zd new file descriptor(s).", open_fds.size());
-
-      // TODO(narayan): This code will be removed in a future android release.
-      std::set<int>::const_iterator it;
-      for (it = open_fds.begin(); it != open_fds.end(); ++it) {
-        const int fd = (*it);
-        FileDescriptorInfo* info = FileDescriptorInfo::createFromFd(fd);
-        if (info == NULL) {
-          // A newly opened file is not on the whitelist. Flag an error and
-          // continue.
-          error = true;
-        } else {
-          // Track the newly opened file.
-          open_fd_map_[fd] = info;
-        }
-      }
-    }
-
-    return !error;
-  }
-
-  static int ParseFd(dirent* e, int dir_fd) {
-    char* end;
-    const int fd = strtol(e->d_name, &end, 10);
-    if ((*end) != '\0') {
-      return -1;
-    }
-
-    // Don't bother with the standard input/output/error, they're handled
-    // specially post-fork anyway.
-    if (fd <= STDERR_FILENO || fd == dir_fd) {
-      return -1;
-    }
-
-    return fd;
-  }
-
-  // Invariant: All values in this unordered_map are non-NULL.
-  std::unordered_map<int, FileDescriptorInfo*> open_fd_map_;
-
-  DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable);
-};
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
new file mode 100644
index 0000000..969d336f3
--- /dev/null
+++ b/core/jni/fd_utils.cpp
@@ -0,0 +1,559 @@
+/*
+ * 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 "fd_utils.h"
+
+#include <algorithm>
+
+#include <fcntl.h>
+#include <grp.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <android-base/strings.h>
+#include <cutils/log.h>
+
+// Static whitelist of open paths that the zygote is allowed to keep open.
+static const char* kPathWhitelist[] = {
+  "/dev/null",
+  "/dev/socket/zygote",
+  "/dev/socket/zygote_secondary",
+  "/dev/socket/webview_zygote",
+  "/sys/kernel/debug/tracing/trace_marker",
+  "/system/framework/framework-res.apk",
+  "/dev/urandom",
+  "/dev/ion",
+  "/dev/dri/renderD129", // Fixes b/31172436
+};
+
+static const char kFdPath[] = "/proc/self/fd";
+
+// static
+FileDescriptorWhitelist* FileDescriptorWhitelist::Get() {
+  if (instance_ == nullptr) {
+    instance_ = new FileDescriptorWhitelist();
+  }
+  return instance_;
+}
+
+bool FileDescriptorWhitelist::IsAllowed(const std::string& path) const {
+  // Check the static whitelist path.
+  for (const auto& whitelist_path : kPathWhitelist) {
+    if (path == whitelist_path)
+      return true;
+  }
+
+  // Check any paths added to the dynamic whitelist.
+  for (const auto& whitelist_path : whitelist_) {
+    if (path == whitelist_path)
+      return true;
+  }
+
+  static const std::string kFrameworksPrefix = "/system/framework/";
+  static const std::string kJarSuffix = ".jar";
+  if (StartsWith(path, kFrameworksPrefix) && EndsWith(path, kJarSuffix)) {
+    return true;
+  }
+
+  // Whitelist files needed for Runtime Resource Overlay, like these:
+  // /system/vendor/overlay/framework-res.apk
+  // /system/vendor/overlay-subdir/pg/framework-res.apk
+  // /vendor/overlay/framework-res.apk
+  // /vendor/overlay/PG/android-framework-runtime-resource-overlay.apk
+  // /data/resource-cache/system@vendor@overlay@framework-res.apk@idmap
+  // /data/resource-cache/system@vendor@overlay-subdir@pg@framework-res.apk@idmap
+  // See AssetManager.cpp for more details on overlay-subdir.
+  static const std::string kOverlayDir = "/system/vendor/overlay/";
+  static const std::string kVendorOverlayDir = "/vendor/overlay";
+  static const std::string kOverlaySubdir = "/system/vendor/overlay-subdir/";
+  static const std::string kApkSuffix = ".apk";
+
+  if ((StartsWith(path, kOverlayDir) || StartsWith(path, kOverlaySubdir)
+       || StartsWith(path, kVendorOverlayDir))
+      && EndsWith(path, kApkSuffix)
+      && path.find("/../") == std::string::npos) {
+    return true;
+  }
+
+  static const std::string kOverlayIdmapPrefix = "/data/resource-cache/";
+  static const std::string kOverlayIdmapSuffix = ".apk@idmap";
+  if (StartsWith(path, kOverlayIdmapPrefix) && EndsWith(path, kOverlayIdmapSuffix)
+      && path.find("/../") == std::string::npos) {
+    return true;
+  }
+
+  // All regular files that are placed under this path are whitelisted automatically.
+  static const std::string kZygoteWhitelistPath = "/vendor/zygote_whitelist/";
+  if (StartsWith(path, kZygoteWhitelistPath) && path.find("/../") == std::string::npos) {
+    return true;
+  }
+
+  return false;
+}
+
+FileDescriptorWhitelist::FileDescriptorWhitelist()
+    : whitelist_() {
+}
+
+// TODO: Call android::base::StartsWith instead of copying the code here.
+// static
+bool FileDescriptorWhitelist::StartsWith(const std::string& str,
+                                         const std::string& prefix) {
+  return str.compare(0, prefix.size(), prefix) == 0;
+}
+
+// TODO: Call android::base::EndsWith instead of copying the code here.
+// static
+bool FileDescriptorWhitelist::EndsWith(const std::string& str,
+                                       const std::string& suffix) {
+  if (suffix.size() > str.size()) {
+    return false;
+  }
+
+  return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0;
+}
+
+FileDescriptorWhitelist* FileDescriptorWhitelist::instance_ = nullptr;
+
+// static
+FileDescriptorInfo* FileDescriptorInfo::CreateFromFd(int fd) {
+  struct stat f_stat;
+  // This should never happen; the zygote should always have the right set
+  // of permissions required to stat all its open files.
+  if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
+    ALOGE("Unable to stat fd %d : %s", fd, strerror(errno));
+    return NULL;
+  }
+
+  const FileDescriptorWhitelist* whitelist = FileDescriptorWhitelist::Get();
+
+  if (S_ISSOCK(f_stat.st_mode)) {
+    std::string socket_name;
+    if (!GetSocketName(fd, &socket_name)) {
+      return NULL;
+    }
+
+    if (!whitelist->IsAllowed(socket_name)) {
+      ALOGE("Socket name not whitelisted : %s (fd=%d)", socket_name.c_str(), fd);
+      return NULL;
+    }
+
+    return new FileDescriptorInfo(fd);
+  }
+
+  // We only handle whitelisted regular files and character devices. Whitelisted
+  // character devices must provide a guarantee of sensible behaviour when
+  // reopened.
+  //
+  // S_ISDIR : Not supported. (We could if we wanted to, but it's unused).
+  // S_ISLINK : Not supported.
+  // S_ISBLK : Not supported.
+  // S_ISFIFO : Not supported. Note that the zygote uses pipes to communicate
+  // with the child process across forks but those should have been closed
+  // before we got to this point.
+  if (!S_ISCHR(f_stat.st_mode) && !S_ISREG(f_stat.st_mode)) {
+    ALOGE("Unsupported st_mode %d", f_stat.st_mode);
+    return NULL;
+  }
+
+  std::string file_path;
+  if (!Readlink(fd, &file_path)) {
+    return NULL;
+  }
+
+  if (!whitelist->IsAllowed(file_path)) {
+    ALOGE("Not whitelisted : %s", file_path.c_str());
+    return NULL;
+  }
+
+  // File descriptor flags : currently on FD_CLOEXEC. We can set these
+  // using F_SETFD - we're single threaded at this point of execution so
+  // there won't be any races.
+  const int fd_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFD));
+  if (fd_flags == -1) {
+    ALOGE("Failed fcntl(%d, F_GETFD) : %s", fd, strerror(errno));
+    return NULL;
+  }
+
+  // File status flags :
+  // - File access mode : (O_RDONLY, O_WRONLY...) we'll pass these through
+  //   to the open() call.
+  //
+  // - File creation flags : (O_CREAT, O_EXCL...) - there's not much we can
+  //   do about these, since the file has already been created. We shall ignore
+  //   them here.
+  //
+  // - Other flags : We'll have to set these via F_SETFL. On linux, F_SETFL
+  //   can only set O_APPEND, O_ASYNC, O_DIRECT, O_NOATIME, and O_NONBLOCK.
+  //   In particular, it can't set O_SYNC and O_DSYNC. We'll have to test for
+  //   their presence and pass them in to open().
+  int fs_flags = TEMP_FAILURE_RETRY(fcntl(fd, F_GETFL));
+  if (fs_flags == -1) {
+    ALOGE("Failed fcntl(%d, F_GETFL) : %s", fd, strerror(errno));
+    return NULL;
+  }
+
+  // File offset : Ignore the offset for non seekable files.
+  const off_t offset = TEMP_FAILURE_RETRY(lseek64(fd, 0, SEEK_CUR));
+
+  // We pass the flags that open accepts to open, and use F_SETFL for
+  // the rest of them.
+  static const int kOpenFlags = (O_RDONLY | O_WRONLY | O_RDWR | O_DSYNC | O_SYNC);
+  int open_flags = fs_flags & (kOpenFlags);
+  fs_flags = fs_flags & (~(kOpenFlags));
+
+  return new FileDescriptorInfo(f_stat, file_path, fd, open_flags, fd_flags, fs_flags, offset);
+}
+
+bool FileDescriptorInfo::Restat() const {
+  struct stat f_stat;
+  if (TEMP_FAILURE_RETRY(fstat(fd, &f_stat)) == -1) {
+    return false;
+  }
+
+  return f_stat.st_ino == stat.st_ino && f_stat.st_dev == stat.st_dev;
+}
+
+bool FileDescriptorInfo::ReopenOrDetach() const {
+  if (is_sock) {
+    return DetachSocket();
+  }
+
+  // NOTE: This might happen if the file was unlinked after being opened.
+  // It's a common pattern in the case of temporary files and the like but
+  // we should not allow such usage from the zygote.
+  const int new_fd = TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags));
+
+  if (new_fd == -1) {
+    ALOGE("Failed open(%s, %d) : %s", file_path.c_str(), open_flags, strerror(errno));
+    return false;
+  }
+
+  if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFD, fd_flags)) == -1) {
+    close(new_fd);
+    ALOGE("Failed fcntl(%d, F_SETFD, %x) : %s", new_fd, fd_flags, strerror(errno));
+    return false;
+  }
+
+  if (TEMP_FAILURE_RETRY(fcntl(new_fd, F_SETFL, fs_flags)) == -1) {
+    close(new_fd);
+    ALOGE("Failed fcntl(%d, F_SETFL, %x) : %s", new_fd, fs_flags, strerror(errno));
+    return false;
+  }
+
+  if (offset != -1 && TEMP_FAILURE_RETRY(lseek64(new_fd, offset, SEEK_SET)) == -1) {
+    close(new_fd);
+    ALOGE("Failed lseek64(%d, SEEK_SET) : %s", new_fd, strerror(errno));
+    return false;
+  }
+
+  if (TEMP_FAILURE_RETRY(dup2(new_fd, fd)) == -1) {
+    close(new_fd);
+    ALOGE("Failed dup2(%d, %d) : %s", fd, new_fd, strerror(errno));
+    return false;
+  }
+
+  close(new_fd);
+
+  return true;
+}
+
+FileDescriptorInfo::FileDescriptorInfo(int fd) :
+  fd(fd),
+  stat(),
+  open_flags(0),
+  fd_flags(0),
+  fs_flags(0),
+  offset(0),
+  is_sock(true) {
+}
+
+FileDescriptorInfo::FileDescriptorInfo(struct stat stat, const std::string& file_path,
+                                       int fd, int open_flags, int fd_flags, int fs_flags,
+                                       off_t offset) :
+  fd(fd),
+  stat(stat),
+  file_path(file_path),
+  open_flags(open_flags),
+  fd_flags(fd_flags),
+  fs_flags(fs_flags),
+  offset(offset),
+  is_sock(false) {
+}
+
+// TODO: Call android::base::Readlink instead of copying the code here.
+// static
+bool FileDescriptorInfo::Readlink(const int fd, std::string* result) {
+  char path[64];
+  snprintf(path, sizeof(path), "/proc/self/fd/%d", fd);
+
+  // Code copied from android::base::Readlink starts here :
+
+  // Annoyingly, the readlink system call returns EINVAL for a zero-sized buffer,
+  // and truncates to whatever size you do supply, so it can't be used to query.
+  // We could call lstat first, but that would introduce a race condition that
+  // we couldn't detect.
+  // ext2 and ext4 both have PAGE_SIZE limitations, so we assume that here.
+  char buf[4096];
+  ssize_t len = readlink(path, buf, sizeof(buf));
+  if (len == -1) return false;
+
+  result->assign(buf, len);
+  return true;
+}
+
+// static
+bool FileDescriptorInfo::GetSocketName(const int fd, std::string* result) {
+  sockaddr_storage ss;
+  sockaddr* addr = reinterpret_cast<sockaddr*>(&ss);
+  socklen_t addr_len = sizeof(ss);
+
+  if (TEMP_FAILURE_RETRY(getsockname(fd, addr, &addr_len)) == -1) {
+    ALOGE("Failed getsockname(%d) : %s", fd, strerror(errno));
+    return false;
+  }
+
+  if (addr->sa_family != AF_UNIX) {
+    ALOGE("Unsupported socket (fd=%d) with family %d", fd, addr->sa_family);
+    return false;
+  }
+
+  const sockaddr_un* unix_addr = reinterpret_cast<const sockaddr_un*>(&ss);
+
+  size_t path_len = addr_len - offsetof(struct sockaddr_un, sun_path);
+  // This is an unnamed local socket, we do not accept it.
+  if (path_len == 0) {
+    ALOGE("Unsupported AF_UNIX socket (fd=%d) with empty path.", fd);
+    return false;
+  }
+
+  // This is a local socket with an abstract address, we do not accept it.
+  if (unix_addr->sun_path[0] == '\0') {
+    ALOGE("Unsupported AF_UNIX socket (fd=%d) with abstract address.", fd);
+    return false;
+  }
+
+  // If we're here, sun_path must refer to a null terminated filesystem
+  // pathname (man 7 unix). Remove the terminator before assigning it to an
+  // std::string.
+  if (unix_addr->sun_path[path_len - 1] ==  '\0') {
+    --path_len;
+  }
+
+  result->assign(unix_addr->sun_path, path_len);
+  return true;
+}
+
+bool FileDescriptorInfo::DetachSocket() const {
+  const int dev_null_fd = open("/dev/null", O_RDWR);
+  if (dev_null_fd < 0) {
+    ALOGE("Failed to open /dev/null : %s", strerror(errno));
+    return false;
+  }
+
+  if (dup2(dev_null_fd, fd) == -1) {
+    ALOGE("Failed dup2 on socket descriptor %d : %s", fd, strerror(errno));
+    return false;
+  }
+
+  if (close(dev_null_fd) == -1) {
+    ALOGE("Failed close(%d) : %s", dev_null_fd, strerror(errno));
+    return false;
+  }
+
+  return true;
+}
+
+// static
+FileDescriptorTable* FileDescriptorTable::Create() {
+  DIR* d = opendir(kFdPath);
+  if (d == NULL) {
+    ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno));
+    return NULL;
+  }
+  int dir_fd = dirfd(d);
+  dirent* e;
+
+  std::unordered_map<int, FileDescriptorInfo*> open_fd_map;
+  while ((e = readdir(d)) != NULL) {
+    const int fd = ParseFd(e, dir_fd);
+    if (fd == -1) {
+      continue;
+    }
+
+    FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd);
+    if (info == NULL) {
+      if (closedir(d) == -1) {
+        ALOGE("Unable to close directory : %s", strerror(errno));
+      }
+      return NULL;
+    }
+    open_fd_map[fd] = info;
+  }
+
+  if (closedir(d) == -1) {
+    ALOGE("Unable to close directory : %s", strerror(errno));
+    return NULL;
+  }
+  return new FileDescriptorTable(open_fd_map);
+}
+
+bool FileDescriptorTable::Restat() {
+  std::set<int> open_fds;
+
+  // First get the list of open descriptors.
+  DIR* d = opendir(kFdPath);
+  if (d == NULL) {
+    ALOGE("Unable to open directory %s: %s", kFdPath, strerror(errno));
+    return false;
+  }
+
+  int dir_fd = dirfd(d);
+  dirent* e;
+  while ((e = readdir(d)) != NULL) {
+    const int fd = ParseFd(e, dir_fd);
+    if (fd == -1) {
+      continue;
+    }
+
+    open_fds.insert(fd);
+  }
+
+  if (closedir(d) == -1) {
+    ALOGE("Unable to close directory : %s", strerror(errno));
+    return false;
+  }
+
+  return RestatInternal(open_fds);
+}
+
+// Reopens all file descriptors that are contained in the table. Returns true
+// if all descriptors were successfully re-opened or detached, and false if an
+// error occurred.
+bool FileDescriptorTable::ReopenOrDetach() {
+  std::unordered_map<int, FileDescriptorInfo*>::const_iterator it;
+  for (it = open_fd_map_.begin(); it != open_fd_map_.end(); ++it) {
+    const FileDescriptorInfo* info = it->second;
+    if (info == NULL || !info->ReopenOrDetach()) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+FileDescriptorTable::FileDescriptorTable(
+    const std::unordered_map<int, FileDescriptorInfo*>& map)
+    : open_fd_map_(map) {
+}
+
+bool FileDescriptorTable::RestatInternal(std::set<int>& open_fds) {
+  bool error = false;
+
+  // Iterate through the list of file descriptors we've already recorded
+  // and check whether :
+  //
+  // (a) they continue to be open.
+  // (b) they refer to the same file.
+  std::unordered_map<int, FileDescriptorInfo*>::iterator it = open_fd_map_.begin();
+  while (it != open_fd_map_.end()) {
+    std::set<int>::const_iterator element = open_fds.find(it->first);
+    if (element == open_fds.end()) {
+      // The entry from the file descriptor table is no longer in the list
+      // of open files. We warn about this condition and remove it from
+      // the list of FDs under consideration.
+      //
+      // TODO(narayan): This will be an error in a future android release.
+      // error = true;
+      // ALOGW("Zygote closed file descriptor %d.", it->first);
+      it = open_fd_map_.erase(it);
+    } else {
+      // The entry from the file descriptor table is still open. Restat
+      // it and check whether it refers to the same file.
+      const bool same_file = it->second->Restat();
+      if (!same_file) {
+        // The file descriptor refers to a different description. We must
+        // update our entry in the table.
+        delete it->second;
+        it->second = FileDescriptorInfo::CreateFromFd(*element);
+        if (it->second == NULL) {
+          // The descriptor no longer no longer refers to a whitelisted file.
+          // We flag an error and remove it from the list of files we're
+          // tracking.
+          error = true;
+          it = open_fd_map_.erase(it);
+        } else {
+          // Successfully restatted the file, move on to the next open FD.
+          ++it;
+        }
+      } else {
+        // It's the same file. Nothing to do here. Move on to the next open
+        // FD.
+        ++it;
+      }
+
+      // Finally, remove the FD from the set of open_fds. We do this last because
+      // |element| will not remain valid after a call to erase.
+      open_fds.erase(element);
+    }
+  }
+
+  if (open_fds.size() > 0) {
+    // The zygote has opened new file descriptors since our last inspection.
+    // We warn about this condition and add them to our table.
+    //
+    // TODO(narayan): This will be an error in a future android release.
+    // error = true;
+    // ALOGW("Zygote opened %zd new file descriptor(s).", open_fds.size());
+
+    // TODO(narayan): This code will be removed in a future android release.
+    std::set<int>::const_iterator it;
+    for (it = open_fds.begin(); it != open_fds.end(); ++it) {
+      const int fd = (*it);
+      FileDescriptorInfo* info = FileDescriptorInfo::CreateFromFd(fd);
+      if (info == NULL) {
+        // A newly opened file is not on the whitelist. Flag an error and
+        // continue.
+        error = true;
+      } else {
+        // Track the newly opened file.
+        open_fd_map_[fd] = info;
+      }
+    }
+  }
+
+  return !error;
+}
+
+// static
+int FileDescriptorTable::ParseFd(dirent* e, int dir_fd) {
+  char* end;
+  const int fd = strtol(e->d_name, &end, 10);
+  if ((*end) != '\0') {
+    return -1;
+  }
+
+  // Don't bother with the standard input/output/error, they're handled
+  // specially post-fork anyway.
+  if (fd <= STDERR_FILENO || fd == dir_fd) {
+    return -1;
+  }
+
+  return fd;
+}
diff --git a/core/jni/fd_utils.h b/core/jni/fd_utils.h
new file mode 100644
index 0000000..9e3afd9
--- /dev/null
+++ b/core/jni/fd_utils.h
@@ -0,0 +1,147 @@
+/*
+ * 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.
+ */
+
+#ifndef FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_
+#define FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_
+
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include <dirent.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+
+#include <android-base/macros.h>
+
+// Whitelist of open paths that the zygote is allowed to keep open.
+//
+// In addition to the paths listed in kPathWhitelist in file_utils.cpp, and
+// paths dynamically added with Allow(), all files ending with ".jar"
+// under /system/framework" are whitelisted. See IsAllowed() for the canonical
+// definition.
+//
+// If the whitelisted path is associated with a regular file or a
+// character device, the file is reopened after a fork with the same
+// offset and mode. If the whilelisted  path is associated with a
+// AF_UNIX socket, the socket will refer to /dev/null after each
+// fork, and all operations on it will fail.
+class FileDescriptorWhitelist {
+ public:
+  // Lazily creates the global whitelist.
+  static FileDescriptorWhitelist* Get();
+
+  // Adds a path to the whitelist.
+  void Allow(const std::string& path) {
+    whitelist_.push_back(path);
+  }
+
+  // Returns true iff. a given path is whitelisted. A path is whitelisted
+  // if it belongs to the whitelist (see kPathWhitelist) or if it's a path
+  // under /system/framework that ends with ".jar" or if it is a system
+  // framework overlay.
+  bool IsAllowed(const std::string& path) const;
+
+ private:
+  FileDescriptorWhitelist();
+
+  static bool StartsWith(const std::string& str, const std::string& prefix);
+
+  static bool EndsWith(const std::string& str, const std::string& suffix);
+
+  static FileDescriptorWhitelist* instance_;
+
+  std::vector<std::string> whitelist_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileDescriptorWhitelist);
+};
+
+// Keeps track of all relevant information (flags, offset etc.) of an
+// open zygote file descriptor.
+class FileDescriptorInfo {
+ public:
+  // Create a FileDescriptorInfo for a given file descriptor. Returns
+  // |NULL| if an error occurred.
+  static FileDescriptorInfo* CreateFromFd(int fd);
+
+  // Checks whether the file descriptor associated with this object
+  // refers to the same description.
+  bool Restat() const;
+
+  bool ReopenOrDetach() const;
+
+  const int fd;
+  const struct stat stat;
+  const std::string file_path;
+  const int open_flags;
+  const int fd_flags;
+  const int fs_flags;
+  const off_t offset;
+  const bool is_sock;
+
+ private:
+  FileDescriptorInfo(int fd);
+
+  FileDescriptorInfo(struct stat stat, const std::string& file_path, int fd, int open_flags,
+                     int fd_flags, int fs_flags, off_t offset);
+
+  static bool Readlink(const int fd, std::string* result);
+
+  // Returns the locally-bound name of the socket |fd|. Returns true
+  // iff. all of the following hold :
+  //
+  // - the socket's sa_family is AF_UNIX.
+  // - the length of the path is greater than zero (i.e, not an unnamed socket).
+  // - the first byte of the path isn't zero (i.e, not a socket with an abstract
+  //   address).
+  static bool GetSocketName(const int fd, std::string* result);
+
+  bool DetachSocket() const;
+
+  DISALLOW_COPY_AND_ASSIGN(FileDescriptorInfo);
+};
+
+// A FileDescriptorTable is a collection of FileDescriptorInfo objects
+// keyed by their FDs.
+class FileDescriptorTable {
+ public:
+  // Creates a new FileDescriptorTable. This function scans
+  // /proc/self/fd for the list of open file descriptors and collects
+  // information about them. Returns NULL if an error occurs.
+  static FileDescriptorTable* Create();
+
+  bool Restat();
+
+  // Reopens all file descriptors that are contained in the table. Returns true
+  // if all descriptors were successfully re-opened or detached, and false if an
+  // error occurred.
+  bool ReopenOrDetach();
+
+ private:
+  FileDescriptorTable(const std::unordered_map<int, FileDescriptorInfo*>& map);
+
+  bool RestatInternal(std::set<int>& open_fds);
+
+  static int ParseFd(dirent* e, int dir_fd);
+
+  // Invariant: All values in this unordered_map are non-NULL.
+  std::unordered_map<int, FileDescriptorInfo*> open_fd_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(FileDescriptorTable);
+};
+
+#endif  // FRAMEWORKS_BASE_CORE_JNI_FD_UTILS_H_
diff --git a/core/jni/include/android_runtime/android_view_Surface.h b/core/jni/include/android_runtime/android_view_Surface.h
index b1e552a..1bc8521 100644
--- a/core/jni/include/android_runtime/android_view_Surface.h
+++ b/core/jni/include/android_runtime/android_view_Surface.h
@@ -42,6 +42,7 @@
     NV16              = 0x10,
     NV21              = 0x11,
     YUY2              = 0x14,
+    RGBA_FP16         = 0x16,
     RAW_SENSOR        = 0x20,
     PRIVATE           = 0x22,
     YUV_420_888       = 0x23,
diff --git a/core/proto/android/content/component_name.proto b/core/proto/android/content/component_name.proto
new file mode 100644
index 0000000..7908af9
--- /dev/null
+++ b/core/proto/android/content/component_name.proto
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+option java_package = "android.content";
+option java_multiple_files = true;
+
+package android.content;
+
+/**
+ * An android.content.ComponentName object.
+ */
+message ComponentNameProto {
+    optional string package_name = 1;
+    optional string class_name = 2;
+}
+
diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto
new file mode 100644
index 0000000..683e7aac
--- /dev/null
+++ b/core/proto/android/content/configuration.proto
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+option java_package = "android.content";
+option java_multiple_files = true;
+
+package android.content;
+
+import "frameworks/base/core/proto/android/content/locale.proto";
+
+/**
+ * An android resource configuration.
+ */
+message ConfigurationProto {
+  optional float font_scale = 1;
+  optional uint32 mcc = 2;
+  optional uint32 mnc = 3;
+  repeated LocaleProto locales = 4;
+  optional uint32 screen_layout = 5;
+  optional uint32 touchscreen = 6;
+  optional uint32 keyboard_hidden = 7;
+  optional uint32 hard_keyboard_hidden = 8;
+  optional uint32 navigation = 9;
+  optional uint32 navigation_hidden = 10;
+  optional uint32 orientation = 11;
+  optional uint32 ui_mode = 12;
+  optional uint32 screen_width_dp = 13;
+  optional uint32 screen_height_dp = 14;
+  optional uint32 smallest_screen_width_dp = 15;
+  optional uint32 density_dpi = 16;
+}
+
diff --git a/core/proto/android/content/locale.proto b/core/proto/android/content/locale.proto
new file mode 100644
index 0000000..55ce68e
--- /dev/null
+++ b/core/proto/android/content/locale.proto
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+option java_package = "android.content";
+option java_multiple_files = true;
+
+package android.content;
+
+message LocaleProto {
+  optional string language = 1;
+  optional string country = 2;
+  optional string variant = 3;
+}
+
diff --git a/core/proto/android/os/incident_proto.proto b/core/proto/android/os/incident_proto.proto
new file mode 100644
index 0000000..1708b81
--- /dev/null
+++ b/core/proto/android/os/incident_proto.proto
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+option java_package = "android.os";
+option java_multiple_files = true;
+
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
+import "frameworks/base/core/proto/android/service/fingerprint_proto.proto";
+
+package android.os;
+
+message IncidentHeaderProto {
+    enum Cause {
+        CAUSE_UNKNOWN = 0;
+        CAUSE_USER = 1;
+        CAUSE_ANR = 2;
+        CAUSE_CRASH = 3;
+    }
+
+    optional Cause cause = 1;
+}
+
+message IncidentProto {
+    // Incident header
+    repeated IncidentHeaderProto header = 1;
+
+    // Device information
+    //optional SystemProperties system_properties = 1000;
+
+    // Linux services
+    //optional Procrank procrank = 2000;
+    //optional PageTypeInfo page_type_info = 2001;
+    //optional KernelWakeSources kernel_wake_sources = 2002;
+
+    // System Services
+    optional android.service.fingerprint.FingerprintServiceDumpProto fingerprint = 3000;
+}
diff --git a/core/proto/android/service/fingerprint_proto.proto b/core/proto/android/service/fingerprint_proto.proto
new file mode 100644
index 0000000..b2c5000
--- /dev/null
+++ b/core/proto/android/service/fingerprint_proto.proto
@@ -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.
+ */
+
+syntax = "proto2";
+
+package android.service.fingerprint;
+
+option java_multiple_files = true;
+
+message FingerprintServiceDumpProto {
+    // Each log may include multiple tuples of (user_id, num_fingerprints).
+    repeated FingerprintUserStatsProto users = 1;
+}
+
+message FingerprintUserStatsProto {
+    // Should be 0, 10, 11, 12, etc. where 0 is the owner.
+    optional int32 user_id = 1;
+
+    // The number of fingerprints registered to this user.
+    optional int32 num_fingerprints = 2;
+
+    // Normal fingerprint authentications (e.g. lockscreen).
+    optional FingerprintActionStatsProto normal = 3;
+
+    // Crypto authentications (e.g. to unlock password storage, make secure
+    // purchases, etc).
+    optional FingerprintActionStatsProto crypto = 4;
+}
+
+message FingerprintActionStatsProto {
+    // Number of accepted fingerprints.
+    optional int32 accept = 1;
+
+    // Number of rejected fingerprints.
+    optional int32 reject = 2;
+
+    // Total number of acquisitions. Should be >= accept+reject due to poor
+    // image acquisition in some cases (too fast, too slow, dirty sensor, etc.)
+    optional int32 acquire = 3;
+
+    // Total number of lockouts.
+    optional int32 lockout = 4;
+}
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 69c7b60..5967c69 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1267,7 +1267,8 @@
          This may be empty if network scoring and recommending isn't supported.
          -->
     <string-array name="config_networkRecommendationPackageNames" translatable="false">
-        <!-- Add packages here -->
+        <!-- The standard AOSP network recommendation provider -->
+        <item>com.android.networkrecommendation</item>
     </string-array>
 
     <!-- Whether to enable Hardware FLP overlay which allows Hardware FLP to be
@@ -2613,6 +2614,9 @@
     <!-- Component that is the default launcher when demo mode is enabled. -->
     <string name="config_demoModeLauncherComponent">com.android.retaildemo/.DemoPlayer</string>
 
+    <!-- Hashed password (SHA-256) used to restrict demo mode operation -->
+    <string name="config_demoModePassword" translatable="false"></string>
+
     <!-- Flag indicating whether round icons should be parsed from the application manifest. -->
     <bool name="config_useRoundIcon">false</bool>
 
@@ -2690,4 +2694,7 @@
          user-set value if toggled by settings so the "Transition animation scale" setting
          should also be hidden if intended to be permanent. -->
     <item name="config_appTransitionAnimationDurationScaleDefault" format="float" type="dimen">1.0</item>
+
+    <!-- Flag indicates that whether non-system apps can be installed on internal storage. -->
+    <bool name="config_allow3rdPartyAppOnInternal">true</bool>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3423f0a..510e6af 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -206,6 +206,12 @@
     <!-- Displayed to tell the user that all service is blocked by access control. -->
     <string name="RestrictedOnAll">All voice/data/SMS services are blocked.</string>
 
+    <!-- Displayed to tell the user that they should switch their network preference. -->
+    <string name="NetworkPreferenceSwitchTitle">Can\u2019t reach network</string>
+    <!-- Displayed to tell the user that they should switch their network preference. -->
+    <string name="NetworkPreferenceSwitchSummary">To improve reception, try changing the type selected at Settings > Cellular networks > Preferred network type."</string>
+
+
     <!-- Displayed to tell the user that peer changed TTY mode -->
     <string name="peerTtyModeFull">Peer requested TTY Mode FULL</string>
     <string name="peerTtyModeHco">Peer requested TTY Mode HCO</string>
@@ -2379,22 +2385,6 @@
     <!-- Appened to express the value is this unit of time. -->
     <string name="years">years</string>
 
-    <!-- Phrase describing a time duration using seconds [CHAR LIMIT=16] -->
-    <plurals name="duration_seconds">
-        <item quantity="one">1 second</item>
-        <item quantity="other"><xliff:g id="count">%d</xliff:g> seconds</item>
-    </plurals>
-    <!-- Phrase describing a time duration using minutes [CHAR LIMIT=16] -->
-    <plurals name="duration_minutes">
-        <item quantity="one">1 minute</item>
-        <item quantity="other"><xliff:g id="count">%d</xliff:g> minutes</item>
-    </plurals>
-    <!-- Phrase describing a time duration using hours [CHAR LIMIT=16] -->
-    <plurals name="duration_hours">
-        <item quantity="one">1 hour</item>
-        <item quantity="other"><xliff:g id="count">%d</xliff:g> hours</item>
-    </plurals>
-
     <!-- A string denoting the current point in time that should be as short as possible. Abbreviations are preferred to full strings as this might be shown repetitively. It is used in the header of notifications. [CHAR LIMIT=8]-->
     <string name="now_string_shortest">now</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index c452c62..7c50f62 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -494,6 +494,8 @@
   <java-symbol type="string" name="RestrictedOnData" />
   <java-symbol type="string" name="RestrictedOnEmergency" />
   <java-symbol type="string" name="RestrictedOnNormal" />
+  <java-symbol type="string" name="NetworkPreferenceSwitchSummary" />
+  <java-symbol type="string" name="NetworkPreferenceSwitchTitle" />
   <java-symbol type="string" name="SetupCallDefault" />
   <java-symbol type="string" name="accept" />
   <java-symbol type="string" name="accessibility_enabled" />
@@ -1126,6 +1128,7 @@
   <java-symbol type="string" name="config_ethernet_tcp_buffers" />
   <java-symbol type="string" name="config_wifi_tcp_buffers" />
   <java-symbol type="string" name="config_demoModeLauncherComponent" />
+  <java-symbol type="string" name="config_demoModePassword" />
   <java-symbol type="string" name="demo_starting_message" />
   <java-symbol type="string" name="demo_restarting_message" />
   <java-symbol type="string" name="conference_call" />
@@ -1133,9 +1136,6 @@
 
 
   <java-symbol type="plurals" name="bugreport_countdown" />
-  <java-symbol type="plurals" name="duration_hours" />
-  <java-symbol type="plurals" name="duration_minutes" />
-  <java-symbol type="plurals" name="duration_seconds" />
   <java-symbol type="plurals" name="last_num_days" />
   <java-symbol type="plurals" name="matches_found" />
   <java-symbol type="plurals" name="restr_pin_countdown" />
@@ -2755,6 +2755,9 @@
 
   <java-symbol type="dimen" name="config_appTransitionAnimationDurationScaleDefault" />
 
-<!-- Network Recommendation -->
+  <!-- Network Recommendation -->
   <java-symbol type="array" name="config_networkRecommendationPackageNames" />
+
+  <!-- Whether allow 3rd party apps on internal storage. -->
+  <java-symbol type="bool" name="config_allow3rdPartyAppOnInternal" />
 </resources>
diff --git a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
index 5af2667..c4d00c6 100644
--- a/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageHelperTests.java
@@ -16,17 +16,28 @@
 
 package android.content.pm;
 
-import static android.net.TrafficStats.MB_IN_BYTES;
-
+import android.content.Context;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.storage.IStorageManager;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
 import com.android.internal.content.PackageHelper;
 
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.os.storage.VolumeInfo.STATE_MOUNTED;
+
 public class PackageHelperTests extends AndroidTestCase {
     private static final boolean localLOGV = true;
     public static final String TAG = "PackageHelperTests";
@@ -35,6 +46,94 @@
     private String fullId;
     private String fullId2;
 
+    private static final String sInternalVolPath = "/data";
+    private static final String sAdoptedVolPath = "/mnt/expand/123";
+    private static final String sPublicVolPath = "/emulated";
+
+    private static final String sInternalVolUuid = StorageManager.UUID_PRIVATE_INTERNAL;
+    private static final String sAdoptedVolUuid = "adopted";
+    private static final String sPublicVolUuid = "emulated";
+
+    private static final long sInternalSize = 20000;
+    private static final long sAdoptedSize = 10000;
+    private static final long sPublicSize = 1000000;
+
+    private static final StorageManager sStorageManager = createStorageManagerMock();
+
+    private static StorageManager createStorageManagerMock() {
+        VolumeInfo internalVol = new VolumeInfo("private",
+                VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/);
+        internalVol.path = sInternalVolPath;
+        internalVol.state = STATE_MOUNTED;
+        internalVol.fsUuid = sInternalVolUuid;
+
+        VolumeInfo adoptedVol = new VolumeInfo("adopted",
+                VolumeInfo.TYPE_PRIVATE, null /*DiskInfo*/, null /*partGuid*/);
+        adoptedVol.path = sAdoptedVolPath;
+        adoptedVol.state = STATE_MOUNTED;
+        adoptedVol.fsUuid = sAdoptedVolUuid;
+
+        VolumeInfo publicVol = new VolumeInfo("public",
+                VolumeInfo.TYPE_PUBLIC, null /*DiskInfo*/, null /*partGuid*/);
+        publicVol.state = STATE_MOUNTED;
+        publicVol.path = sPublicVolPath;
+        publicVol.fsUuid = sPublicVolUuid;
+
+        List<VolumeInfo> volumes = new ArrayList<>();
+        volumes.add(internalVol);
+        volumes.add(adoptedVol);
+        volumes.add(publicVol);
+
+        StorageManager storageManager = Mockito.mock(StorageManager.class);
+        Mockito.when(storageManager.getVolumes()).thenReturn(volumes);
+
+        File internalFile = new File(sInternalVolPath);
+        File adoptedFile = new File(sAdoptedVolPath);
+        File publicFile = new File(sPublicVolPath);
+        Mockito.when(storageManager.getStorageBytesUntilLow(internalFile)).thenReturn(sInternalSize);
+        Mockito.when(storageManager.getStorageBytesUntilLow(adoptedFile)).thenReturn(sAdoptedSize);
+        Mockito.when(storageManager.getStorageBytesUntilLow(publicFile)).thenReturn(sPublicSize);
+        return storageManager;
+    }
+
+    private static final class MockedInterface extends PackageHelper.TestableInterface {
+        private boolean mForceAllowOnExternal = false;
+        private boolean mAllow3rdPartyOnInternal = true;
+        private ApplicationInfo mApplicationInfo = null;
+
+        public void setMockValues(ApplicationInfo applicationInfo,
+                boolean forceAllowOnExternal, boolean allow3rdPartyOnInternal) {
+            mForceAllowOnExternal = forceAllowOnExternal;
+            mAllow3rdPartyOnInternal = allow3rdPartyOnInternal;
+            mApplicationInfo = applicationInfo;
+        }
+
+        @Override
+        public StorageManager getStorageManager(Context context) {
+            return sStorageManager;
+        }
+
+        @Override
+        public boolean getForceAllowOnExternalSetting(Context context) {
+            return mForceAllowOnExternal;
+        }
+
+        @Override
+        public boolean getAllow3rdPartyOnInternalConfig(Context context) {
+            return mAllow3rdPartyOnInternal;
+        }
+
+        @Override
+        public ApplicationInfo getExistingAppInfo(Context context, String packagename) {
+            return mApplicationInfo;
+        }
+
+        @Override
+        public File getDataDirectory() {
+            return new File(sInternalVolPath);
+        }
+    }
+
     private IStorageManager getSm() {
         IBinder service = ServiceManager.getService("mount");
         if (service != null) {
@@ -131,4 +230,328 @@
         };
         return r;
     }
+
+    public void testResolveInstallVolumeInternal_SystemApp() throws IOException {
+        ApplicationInfo systemAppInfo = new ApplicationInfo();
+        systemAppInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        // All test cases for when the system app fits on internal.
+        MockedInterface mockedInterface = new MockedInterface();
+        mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        String volume = null;
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
+
+        mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
+
+        mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
+
+        mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                1 /*install location*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(StorageManager.UUID_PRIVATE_INTERNAL, volume);
+
+
+        // All test cases for when the system app does not fit on internal.
+        // Exception should be thrown.
+        mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        try {
+            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                    1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
+            fail("Expected exception in resolveInstallVolume was not thrown");
+        } catch(IOException e) {
+            // expected
+        }
+
+        mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        try {
+            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                    1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
+            fail("Expected exception in resolveInstallVolume was not thrown");
+        } catch(IOException e) {
+            // expected
+        }
+
+        mockedInterface.setMockValues(systemAppInfo, false /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        try {
+            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                    1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
+            fail("Expected exception in resolveInstallVolume was not thrown");
+        } catch(IOException e) {
+            // expected
+        }
+
+        mockedInterface.setMockValues(systemAppInfo, true /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        try {
+            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                    1 /*install location*/, 1000000 /*size bytes*/, mockedInterface);
+            fail("Expected exception in resolveInstallVolume was not thrown");
+        } catch(IOException e) {
+            // expected
+        }
+    }
+
+    public void testResolveInstallVolumeInternal_3rdParty_existing_not_too_big()
+            throws IOException {
+        // Existing apps always stay on the same volume.
+        // Test cases for existing app on internal.
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.volumeUuid = sInternalVolUuid;
+        MockedInterface mockedInterface = new MockedInterface();
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        String volume = null;
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(sInternalVolUuid, volume);
+
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(sInternalVolUuid, volume);
+    }
+
+    public void testResolveInstallVolumeInternal_3rdParty_existing_not_too_big_adopted()
+            throws IOException {
+        // Test cases for existing app on the adopted media.
+        ApplicationInfo appInfo = new ApplicationInfo();
+        MockedInterface mockedInterface = new MockedInterface();
+        String volume;
+        appInfo.volumeUuid = sAdoptedVolUuid;
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(sAdoptedVolUuid, volume);
+
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(sAdoptedVolUuid, volume);
+
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(sAdoptedVolUuid, volume);
+
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                0 /*install location*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(sAdoptedVolUuid, volume);
+    }
+
+    public void testResolveInstallVolumeAdopted_3rdParty_existing_too_big() {
+        // Test: update size too big, will throw exception.
+        ApplicationInfo appInfo = new ApplicationInfo();
+        appInfo.volumeUuid = sAdoptedVolUuid;
+
+        MockedInterface mockedInterface = new MockedInterface();
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        try {
+            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                    0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
+            fail("Expected exception was not thrown " + appInfo.volumeUuid);
+        } catch (IOException e) {
+            //expected
+        }
+
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        try {
+            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                    0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
+            fail("Expected exception was not thrown " + appInfo.volumeUuid);
+        } catch (IOException e) {
+            //expected
+        }
+
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        try {
+            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                    0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
+            fail("Expected exception was not thrown " + appInfo.volumeUuid);
+        } catch (IOException e) {
+            //expected
+        }
+
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        try {
+            PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                    0 /*install location*/, 10000001 /*BIG size, won't fit*/, mockedInterface);
+            fail("Expected exception was not thrown " + appInfo.volumeUuid);
+        } catch (IOException e) {
+            //expected
+        }
+    }
+
+    public void testResolveInstallVolumeInternal_3rdParty_auto() throws IOException {
+        ApplicationInfo appInfo = null;
+        MockedInterface mockedInterface = new MockedInterface();
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        String volume = null;
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
+        // Should return the volume with bigger available space.
+        assertEquals(sInternalVolUuid, volume);
+
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
+        // Should return the volume with bigger available space.
+        assertEquals(sInternalVolUuid, volume);
+
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
+        // Should return the volume with bigger available space.
+        assertEquals(sAdoptedVolUuid, volume);
+
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
+        // Should return the volume with bigger available space.
+        assertEquals(sAdoptedVolUuid, volume);
+
+
+    }
+
+    public void testResolveInstallVolumeInternal_3rdParty_internal_only() throws IOException {
+        ApplicationInfo appInfo = null;
+        MockedInterface mockedInterface = new MockedInterface();
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        String volume = null;
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(sInternalVolUuid, volume);
+
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                1 /*install location internal ONLY*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(sInternalVolUuid, volume);
+
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        try {
+            volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                    1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
+            fail("Expected exception in resolveInstallVolume was not thrown");
+        } catch (IOException e) {
+            //expected
+        }
+
+        appInfo = null;
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        volume = null;
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(sAdoptedVolUuid, volume);
+    }
+
+    public void testResolveInstallVolumeInternal_3rdParty_not_allowed_on_internal()
+        throws IOException {
+        ApplicationInfo appInfo = null;
+        MockedInterface mockedInterface = new MockedInterface();
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        String volume = null;
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
+        // Should return the non-internal volume.
+        assertEquals(sAdoptedVolUuid, volume);
+
+        appInfo = null;
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                0 /*install location auto*/, 1000 /*size bytes*/, mockedInterface);
+        // Should return the non-internal volume.
+        assertEquals(sAdoptedVolUuid, volume);
+    }
+
+    public void testResolveInstallVolumeInternal_3rdParty_internal_only_too_big() {
+        ApplicationInfo appInfo = null;
+        MockedInterface mockedInterface = new MockedInterface();
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                true /*allow 3rd party on internal*/);
+        String volume = null;
+        try {
+            volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                    1 /*install location internal ONLY*/,
+                    1000000 /*size too big*/, mockedInterface);
+            fail("Expected exception in resolveInstallVolume was not thrown");
+        } catch (IOException e) {
+            //expected
+        }
+    }
+
+    public void testResolveInstallVolumeInternal_3rdParty_internal_only_not_allowed()
+        throws IOException {
+        ApplicationInfo appInfo = null;
+        MockedInterface mockedInterface = new MockedInterface();
+        mockedInterface.setMockValues(appInfo, false /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        String volume = null;
+        try {
+            volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+                    1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
+            fail("Expected exception in resolveInstallVolume was not thrown");
+        } catch (IOException e) {
+            //expected
+        }
+
+        appInfo = null;
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        volume = null;
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(sAdoptedVolUuid, volume);
+
+    }
+
+    public void testResolveInstallVolumeInternal_3rdParty_internal_only_forced_to_external()
+        throws IOException {
+        // New/existing installation: New
+        // app request location: Internal Only
+        // 3rd party allowed on internal: False
+        // Force allow external in setting: True
+        // Size fit? Yes
+        ApplicationInfo appInfo = null;
+        MockedInterface mockedInterface = new MockedInterface();
+        mockedInterface.setMockValues(appInfo, true /*force allow on external*/,
+                false /*allow 3rd party on internal*/);
+        String volume = null;
+        volume = PackageHelper.resolveInstallVolume(getContext(), "package.name",
+            1 /*install location internal only*/, 1000 /*size bytes*/, mockedInterface);
+        assertEquals(sAdoptedVolUuid, volume);
+    }
 }
diff --git a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
index 02c2517..5bfff26 100644
--- a/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
+++ b/core/tests/coretests/src/android/net/NetworkScorerAppManagerTest.java
@@ -16,32 +16,33 @@
 
 package android.net;
 
+import static org.mockito.Mockito.when;
+
 import android.Manifest.permission;
+import android.content.ContentResolver;
 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.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
-import android.os.UserHandle;
+import android.provider.Settings;
 import android.test.InstrumentationTestCase;
-
+import com.android.internal.R;
+import java.util.List;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
 public class NetworkScorerAppManagerTest extends InstrumentationTestCase {
     @Mock private Context mMockContext;
     @Mock private PackageManager mMockPm;
-
+    @Mock private Resources mResources;
+    @Mock private ContentResolver mContentResolver;
+    private Context mTargetContext;
     private NetworkScorerAppManager mNetworkScorerAppManager;
 
     @Override
@@ -49,154 +50,161 @@
         super.setUp();
 
         // Configuration needed to make mockito/dexcache work.
-        System.setProperty("dexmaker.dexcache",
-                getInstrumentation().getTargetContext().getCacheDir().getPath());
+        mTargetContext = getInstrumentation().getTargetContext();
+        System.setProperty("dexmaker.dexcache", mTargetContext.getCacheDir().getPath());
         ClassLoader newClassLoader = getInstrumentation().getClass().getClassLoader();
         Thread.currentThread().setContextClassLoader(newClassLoader);
 
         MockitoAnnotations.initMocks(this);
-        Mockito.when(mMockContext.getPackageManager()).thenReturn(mMockPm);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPm);
+        when(mMockContext.getResources()).thenReturn(mResources);
+        when(mMockContext.getContentResolver()).thenReturn(mTargetContext.getContentResolver());
         mNetworkScorerAppManager = new NetworkScorerAppManager(mMockContext);
     }
 
-    public void testGetAllValidScorers() throws Exception {
-        // Package 1 - Valid scorer.
-        ResolveInfoHolder package1 = buildResolveInfo("package1", 1, true, true, false, false);
-
-        // Package 2 - Receiver does not have BROADCAST_NETWORK_PRIVILEGED permission.
-        ResolveInfoHolder package2 = buildResolveInfo("package2", 2, false, true, false, false);
-
-        // Package 3 - App does not have SCORE_NETWORKS permission.
-        ResolveInfoHolder package3 = buildResolveInfo("package3", 3, true, false, false, false);
-
-        // Package 4 - Valid scorer w/ optional config activity.
-        ResolveInfoHolder package4 = buildResolveInfo("package4", 4, true, true, true, false);
-
-        // Package 5 - Valid scorer w/ optional service to bind to.
-        ResolveInfoHolder package5 = buildResolveInfo("package5", 5, true, true, false, true);
-
-        List<ResolveInfoHolder> scorers = new ArrayList<>();
-        scorers.add(package1);
-        scorers.add(package2);
-        scorers.add(package3);
-        scorers.add(package4);
-        scorers.add(package5);
-        setScorers(scorers);
-
-        Iterator<NetworkScorerAppData> result =
-                mNetworkScorerAppManager.getAllValidScorers().iterator();
-
-        assertTrue(result.hasNext());
-        NetworkScorerAppData next = result.next();
-        assertEquals("package1", next.mPackageName);
-        assertEquals(1, next.mPackageUid);
-        assertNull(next.mConfigurationActivityClassName);
-
-        assertTrue(result.hasNext());
-        next = result.next();
-        assertEquals("package4", next.mPackageName);
-        assertEquals(4, next.mPackageUid);
-        assertEquals(".ConfigActivity", next.mConfigurationActivityClassName);
-
-        assertTrue(result.hasNext());
-        next = result.next();
-        assertEquals("package5", next.mPackageName);
-        assertEquals(5, next.mPackageUid);
-        assertEquals(".ScoringService", next.mScoringServiceClassName);
-
-        assertFalse(result.hasNext());
+    public void testGetPotentialRecommendationProviderPackages_emptyConfig() throws Exception {
+        setNetworkRecommendationPackageNames(/*no configured packages*/);
+        assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty());
     }
 
-    private void setScorers(List<ResolveInfoHolder> scorers) {
-        List<ResolveInfo> receivers = new ArrayList<>();
-        for (final ResolveInfoHolder scorer : scorers) {
-            receivers.add(scorer.scorerResolveInfo);
-            if (scorer.configActivityResolveInfo != null) {
-                // This scorer has a config activity.
-                Mockito.when(mMockPm.queryIntentActivities(
-                        Mockito.argThat(new ArgumentMatcher<Intent>() {
-                            @Override
-                            public boolean matches(Object object) {
-                                Intent intent = (Intent) object;
-                                return NetworkScoreManager.ACTION_CUSTOM_ENABLE.equals(
-                                        intent.getAction())
-                                        && scorer.scorerResolveInfo.activityInfo.packageName.equals(
-                                                intent.getPackage());
-                            }
-                        }), Mockito.eq(0))).thenReturn(
-                                Collections.singletonList(scorer.configActivityResolveInfo));
-            }
+    public void testGetPotentialRecommendationProviderPackages_permissionNotGranted()
+            throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksDenied("package1");
 
-            if (scorer.serviceResolveInfo != null) {
-                // This scorer has a service to bind to
-                Mockito.when(mMockPm.resolveService(
-                        Mockito.argThat(new ArgumentMatcher<Intent>() {
-                            @Override
-                            public boolean matches(Object object) {
-                                Intent intent = (Intent) object;
-                                return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(
-                                        intent.getAction())
-                                        && scorer.scorerResolveInfo.activityInfo.packageName.equals(
-                                        intent.getPackage());
-                            }
-                        }), Mockito.eq(0))).thenReturn(scorer.serviceResolveInfo);
-            }
+        assertTrue(mNetworkScorerAppManager.getPotentialRecommendationProviderPackages().isEmpty());
+    }
+
+    public void testGetPotentialRecommendationProviderPackages_permissionGranted()
+            throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksGranted("package1");
+
+        List<String> potentialProviderPackages =
+                mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
+
+        assertFalse(potentialProviderPackages.isEmpty());
+        assertEquals("package1", potentialProviderPackages.get(0));
+    }
+
+    public void testGetPotentialRecommendationProviderPackages_multipleConfigured()
+            throws Exception {
+        setNetworkRecommendationPackageNames("package1", "package2");
+        mockScoreNetworksDenied("package1");
+        mockScoreNetworksGranted("package2");
+
+        List<String> potentialProviderPackages =
+                mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
+
+        assertEquals(1, potentialProviderPackages.size());
+        assertEquals("package2", potentialProviderPackages.get(0));
+    }
+
+    public void testGetNetworkRecommendationProviderData_noPotentialPackages() throws Exception {
+        setNetworkRecommendationPackageNames(/*no configured packages*/);
+        assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData());
+    }
+
+    public void testGetNetworkRecommendationProviderData_serviceMissing() throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksGranted("package1");
+
+        assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData());
+    }
+
+    public void testGetNetworkRecommendationProviderData_scoreNetworksNotGranted()
+            throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksDenied("package1");
+        mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
+
+        assertNull(mNetworkScorerAppManager.getNetworkRecommendationProviderData());
+    }
+
+    public void testGetNetworkRecommendationProviderData_available() throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksGranted("package1");
+        mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
+
+        NetworkScorerAppData appData =
+                mNetworkScorerAppManager.getNetworkRecommendationProviderData();
+        assertNotNull(appData);
+        assertEquals("package1", appData.packageName);
+        assertEquals(924, appData.packageUid);
+        assertEquals(".RecommendationService", appData.recommendationServiceClassName);
+    }
+
+    public void testGetActiveScorer_providerAvailable() throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksGranted("package1");
+        mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
+
+        ContentResolver cr = mTargetContext.getContentResolver();
+        Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1);
+
+        final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+        assertNotNull(activeScorer);
+        assertEquals("package1", activeScorer.packageName);
+        assertEquals(924, activeScorer.packageUid);
+        assertEquals(".RecommendationService", activeScorer.recommendationServiceClassName);
+    }
+
+    public void testGetActiveScorer_providerNotAvailable()
+            throws Exception {
+        ContentResolver cr = mTargetContext.getContentResolver();
+        Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 1);
+
+        final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+        assertNull(activeScorer);
+    }
+
+    public void testGetActiveScorer_recommendationsDisabled() throws Exception {
+        setNetworkRecommendationPackageNames("package1");
+        mockScoreNetworksGranted("package1");
+        mockRecommendationServiceAvailable("package1", 924 /* packageUid */);
+        ContentResolver cr = mTargetContext.getContentResolver();
+        Settings.Global.putInt(cr, Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0);
+
+        final NetworkScorerAppData activeScorer = mNetworkScorerAppManager.getActiveScorer();
+        assertNull(activeScorer);
+    }
+
+    private void setNetworkRecommendationPackageNames(String... names) {
+        if (names == null) {
+            names = new String[0];
         }
+        when(mResources.getStringArray(R.array.config_networkRecommendationPackageNames))
+                .thenReturn(names);
+    }
 
-        Mockito.when(mMockPm.queryBroadcastReceiversAsUser(
+    private void mockScoreNetworksGranted(String packageName) {
+        when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void mockScoreNetworksDenied(String packageName) {
+        when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName))
+                .thenReturn(PackageManager.PERMISSION_DENIED);
+    }
+
+    private void mockRecommendationServiceAvailable(final String packageName, int packageUid) {
+        final ResolveInfo serviceInfo = new ResolveInfo();
+        serviceInfo.serviceInfo = new ServiceInfo();
+        serviceInfo.serviceInfo.name = ".RecommendationService";
+        serviceInfo.serviceInfo.packageName = packageName;
+        serviceInfo.serviceInfo.applicationInfo = new ApplicationInfo();
+        serviceInfo.serviceInfo.applicationInfo.uid = packageUid;
+
+        final int flags = 0;
+        when(mMockPm.resolveService(
                 Mockito.argThat(new ArgumentMatcher<Intent>() {
                     @Override
                     public boolean matches(Object object) {
                         Intent intent = (Intent) object;
-                        return NetworkScoreManager.ACTION_SCORE_NETWORKS.equals(intent.getAction());
+                        return NetworkScoreManager.ACTION_RECOMMEND_NETWORKS
+                                .equals(intent.getAction())
+                                && packageName.equals(intent.getPackage());
                     }
-                }), Mockito.eq(0), Mockito.eq(UserHandle.USER_SYSTEM)))
-                .thenReturn(receivers);
-    }
-
-    private ResolveInfoHolder buildResolveInfo(String packageName, int packageUid,
-            boolean hasReceiverPermission, boolean hasScorePermission, boolean hasConfigActivity,
-            boolean hasServiceInfo) throws Exception {
-        Mockito.when(mMockPm.checkPermission(permission.SCORE_NETWORKS, packageName))
-                .thenReturn(hasScorePermission ?
-                        PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED);
-
-        ResolveInfo resolveInfo = new ResolveInfo();
-        resolveInfo.activityInfo = new ActivityInfo();
-        resolveInfo.activityInfo.packageName = packageName;
-        resolveInfo.activityInfo.applicationInfo = new ApplicationInfo();
-        resolveInfo.activityInfo.applicationInfo.uid = packageUid;
-        if (hasReceiverPermission) {
-            resolveInfo.activityInfo.permission = permission.BROADCAST_NETWORK_PRIVILEGED;
-        }
-
-        ResolveInfo configActivityInfo = null;
-        if (hasConfigActivity) {
-            configActivityInfo = new ResolveInfo();
-            configActivityInfo.activityInfo = new ActivityInfo();
-            configActivityInfo.activityInfo.name = ".ConfigActivity";
-        }
-
-        ResolveInfo serviceInfo = null;
-        if (hasServiceInfo) {
-            serviceInfo = new ResolveInfo();
-            serviceInfo.serviceInfo = new ServiceInfo();
-            serviceInfo.serviceInfo.name = ".ScoringService";
-        }
-
-        return new ResolveInfoHolder(resolveInfo, configActivityInfo, serviceInfo);
-    }
-
-    private static class ResolveInfoHolder {
-        final ResolveInfo scorerResolveInfo;
-        final ResolveInfo configActivityResolveInfo;
-        final ResolveInfo serviceResolveInfo;
-
-        public ResolveInfoHolder(ResolveInfo scorerResolveInfo,
-                ResolveInfo configActivityResolveInfo, ResolveInfo serviceResolveInfo) {
-            this.scorerResolveInfo = scorerResolveInfo;
-            this.configActivityResolveInfo = configActivityResolveInfo;
-            this.serviceResolveInfo = serviceResolveInfo;
-        }
+                }), Mockito.eq(flags))).thenReturn(serviceInfo);
     }
 }
diff --git a/core/tests/coretests/src/android/net/RecommendationRequestTest.java b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
new file mode 100644
index 0000000..31560b0
--- /dev/null
+++ b/core/tests/coretests/src/android/net/RecommendationRequestTest.java
@@ -0,0 +1,84 @@
+package android.net;
+
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+
+public class RecommendationRequestTest extends AndroidTestCase {
+    private ScanResult[] mScanResults;
+    private WifiConfiguration mConfiguration;
+    private NetworkCapabilities mCapabilities;
+
+    @Override
+    public void setUp() throws Exception {
+        mScanResults = new ScanResult[2];
+        mScanResults[0] = new ScanResult();
+        mScanResults[1] = new ScanResult(
+                "ssid",
+                "bssid",
+                0L /*hessid*/,
+                1 /*anqpDominId*/,
+                "caps",
+                2 /*level*/,
+                3 /*frequency*/,
+                4L /*tsf*/,
+                5 /*distCm*/,
+                6 /*distSdCm*/,
+                7 /*channelWidth*/,
+                8 /*centerFreq0*/,
+                9 /*centerFreq1*/,
+                false /*is80211McRTTResponder*/);
+        mConfiguration = new WifiConfiguration();
+        mConfiguration.SSID = "RecommendationRequestTest";
+        mCapabilities = new NetworkCapabilities()
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED);
+    }
+
+    public void testParceling() throws Exception {
+        RecommendationRequest request = new RecommendationRequest.Builder()
+                .setCurrentRecommendedWifiConfig(mConfiguration)
+                .setScanResults(mScanResults)
+                .setNetworkCapabilities(mCapabilities)
+                .build();
+
+        RecommendationRequest parceled = passThroughParcel(request);
+        assertEquals(request.getCurrentSelectedConfig().SSID,
+                parceled.getCurrentSelectedConfig().SSID);
+        assertEquals(request.getRequiredCapabilities(), parceled.getRequiredCapabilities());
+        ScanResult[] parceledScanResults = parceled.getScanResults();
+        assertNotNull(parceledScanResults);
+        assertEquals(mScanResults.length, parceledScanResults.length);
+        for (int i = 0; i < mScanResults.length; i++) {
+            assertEquals(mScanResults[i].SSID, parceledScanResults[i].SSID);
+        }
+    }
+
+    public void testParceling_nullScanResults() throws Exception {
+        RecommendationRequest request = new RecommendationRequest.Builder()
+                .setCurrentRecommendedWifiConfig(mConfiguration)
+                .setNetworkCapabilities(mCapabilities)
+                .build();
+
+        RecommendationRequest parceled = passThroughParcel(request);
+        assertEquals(request.getCurrentSelectedConfig().SSID,
+                parceled.getCurrentSelectedConfig().SSID);
+        assertEquals(request.getRequiredCapabilities(), parceled.getRequiredCapabilities());
+        ScanResult[] parceledScanResults = parceled.getScanResults();
+        assertNull(parceledScanResults);
+    }
+
+    private RecommendationRequest passThroughParcel(RecommendationRequest request) {
+        Parcel p = Parcel.obtain();
+        RecommendationRequest output = null;
+        try {
+            request.writeToParcel(p, 0);
+            p.setDataPosition(0);
+            output = RecommendationRequest.CREATOR.createFromParcel(p);
+        } finally {
+            p.recycle();
+        }
+        assertNotNull(output);
+        return output;
+    }
+}
diff --git a/core/tests/coretests/src/android/view/ViewCaptureTest.java b/core/tests/coretests/src/android/view/ViewCaptureTest.java
index 15cfe23..1b4d4a2 100644
--- a/core/tests/coretests/src/android/view/ViewCaptureTest.java
+++ b/core/tests/coretests/src/android/view/ViewCaptureTest.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static org.junit.Assert.assertTrue;
+
 import android.app.Activity;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -32,8 +34,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import static org.junit.Assert.assertTrue;
-
 @RunWith(AndroidJUnit4.class)
 public class ViewCaptureTest {
 
@@ -70,6 +70,7 @@
 
     private void testCreateSnapshot(boolean skipChildren, int goldenResId) {
         Bitmap result = mViewToCapture.createSnapshot(Bitmap.Config.ARGB_8888, 0, skipChildren);
+        result.setHasAlpha(false); // resource will have no alpha, since content is opaque
         Bitmap golden = BitmapFactory.decodeResource(mActivity.getResources(), goldenResId);
         assertTrue(golden.sameAs(result));
     }
diff --git a/docs/html/reference/images/graphics/colorspace_ucs.png b/docs/html/reference/images/graphics/colorspace_ucs.png
new file mode 100644
index 0000000..3e0f0c6
--- /dev/null
+++ b/docs/html/reference/images/graphics/colorspace_ucs.png
Binary files differ
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index cd75fe9..a041a28 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -140,7 +140,7 @@
      * Native bitmap has been reconfigured, so set premult and cached
      * width/height values
      */
-    // called from JNI
+    @SuppressWarnings("unused") // called from JNI
     void reinit(int width, int height, boolean requestPremultiplied) {
         mWidth = width;
         mHeight = height;
@@ -465,6 +465,15 @@
          */
         ARGB_8888   (5),
 
+        /**
+         * Each pixels is stored on 8 bytes. Each channel (RGB and alpha
+         * for translucency) is stored as a
+         * {@link android.util.Half half-precision floating point value}.
+         *
+         * This configuration is particularly suited for wide-gamut and
+         * HDR content.
+         */
+        RGBA_F16    (6),
 
         /**
          * Special configuration, when bitmap is stored only in graphic memory.
@@ -473,12 +482,12 @@
          * It is optimal for cases, when the only operation with the bitmap is to draw it on a
          * screen.
          */
-        HARDWARE    (6);
+        HARDWARE    (7);
 
         final int nativeInt;
 
         private static Config sConfigs[] = {
-            null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, HARDWARE
+            null, ALPHA_8, null, RGB_565, ARGB_4444, ARGB_8888, RGBA_F16, HARDWARE
         };
 
         Config(int ni) {
@@ -760,6 +769,9 @@
                 case ALPHA_8:
                     newConfig = Config.ALPHA_8;
                     break;
+                case RGBA_F16:
+                    newConfig = Config.RGBA_F16;
+                    break;
                 //noinspection deprecation
                 case ARGB_4444:
                 case ARGB_8888:
@@ -781,8 +793,13 @@
             neww = Math.round(deviceR.width());
             newh = Math.round(deviceR.height());
 
-            bitmap = createBitmap(neww, newh, transformed ? Config.ARGB_8888 : newConfig,
-                    transformed || source.hasAlpha());
+            Config transformedConfig = newConfig;
+            if (transformed) {
+                if (transformedConfig != Config.ARGB_8888 && transformedConfig != Config.RGBA_F16) {
+                    transformedConfig = Config.ARGB_8888;
+                }
+            }
+            bitmap = createBitmap(neww, newh, transformedConfig, transformed || source.hasAlpha());
 
             canvas.translate(-deviceR.left, -deviceR.top);
             canvas.concat(m);
@@ -845,14 +862,14 @@
      * @param width    The width of the bitmap
      * @param height   The height of the bitmap
      * @param config   The bitmap config to create.
-     * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the
-     *                 bitmap as opaque. Doing so will clear the bitmap in black
+     * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
+     *                 mark the bitmap as opaque. Doing so will clear the bitmap in black
      *                 instead of transparent.
      *
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
      */
-    private static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) {
+    public static Bitmap createBitmap(int width, int height, Config config, boolean hasAlpha) {
         return createBitmap(null, width, height, config, hasAlpha);
     }
 
@@ -865,14 +882,14 @@
      * @param width    The width of the bitmap
      * @param height   The height of the bitmap
      * @param config   The bitmap config to create.
-     * @param hasAlpha If the bitmap is ARGB_8888 this flag can be used to mark the
-     *                 bitmap as opaque. Doing so will clear the bitmap in black
+     * @param hasAlpha If the bitmap is ARGB_8888 or RGBA_16F this flag can be used to
+     *                 mark the bitmap as opaque. Doing so will clear the bitmap in black
      *                 instead of transparent.
      *
      * @throws IllegalArgumentException if the width or height are <= 0, or if
      *         Config is Config.HARDWARE, because hardware bitmaps are always immutable
      */
-    private static Bitmap createBitmap(DisplayMetrics display, int width, int height,
+    public static Bitmap createBitmap(DisplayMetrics display, int width, int height,
             Config config, boolean hasAlpha) {
         if (width <= 0 || height <= 0) {
             throw new IllegalArgumentException("width and height must be > 0");
@@ -885,7 +902,7 @@
             bm.mDensity = display.densityDpi;
         }
         bm.setHasAlpha(hasAlpha);
-        if (config == Config.ARGB_8888 && !hasAlpha) {
+        if ((config == Config.ARGB_8888 || config == Config.RGBA_F16) && !hasAlpha) {
             nativeErase(bm.mNativePtr, 0xff000000);
         }
         // No need to initialize the bitmap to zeroes with other configs;
@@ -1526,7 +1543,7 @@
 
     /**
      * <p>Replace pixels in the bitmap with the colors in the array. Each element
-     * in the array is a packed int prepresenting a non-premultiplied ARGB
+     * in the array is a packed int representing a non-premultiplied ARGB
      * {@link Color}.</p>
      *
      * @param pixels   The colors to write to the bitmap
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 7dc5de3..d968516 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -1671,6 +1671,28 @@
     }
 
     /**
+     * Converts values from CIE xyY to CIE L*u*v*. Y is assumed to be 1 so the
+     * input xyY array only contains the x and y components. After this method
+     * returns, the xyY array contains the converted u and v components.
+     *
+     * @param xyY The xyY value to convert to XYZ, cannot be null,
+     *            length must be a multiple of 2
+     */
+    private static void xyYToUv(@NonNull @Size(multiple = 2) float[] xyY) {
+        for (int i = 0; i < xyY.length; i += 2) {
+            float x = xyY[i];
+            float y = xyY[i + 1];
+
+            float d = -2.0f * x + 12.0f * y + 3;
+            float u = (4.0f * x) / d;
+            float v = (9.0f * y) / d;
+
+            xyY[i] = u;
+            xyY[i + 1] = v;
+        }
+    }
+
+    /**
      * <p>Computes the chromatic adaptation transform from the specified
      * source white point to the specified destination white point.</p>
      *
@@ -3162,10 +3184,10 @@
     /**
      * <p>A color space renderer can be used to visualize and compare the gamut and
      * white point of one or more color spaces. The output is an sRGB {@link Bitmap}
-     * showing a CIE 1931 xyY chromaticity diagram.</p>
+     * showing a CIE 1931 xyY or a CIE 1976 UCS chromaticity diagram.</p>
      *
      * <p>The following code snippet shows how to compare the {@link Named#SRGB}
-     * and {@link Named#DCI_P3} color spaces:</p>
+     * and {@link Named#DCI_P3} color spaces in a CIE 1931 diagram:</p>
      *
      * <pre class="prettyprint">
      * Bitmap bitmap = ColorSpace.createRenderer()
@@ -3188,12 +3210,18 @@
      */
     public static class Renderer {
         private static final int NATIVE_SIZE = 1440;
+        private static final float UCS_SCALE = 9.0f / 6.0f;
+
+        // Number of subdivision of the inside of the spectral locus
+        private static final int CHROMATICITY_RESOLUTION = 32;
+        private static final double ONE_THIRD = 1.0 / 3.0;
 
         @IntRange(from = 128, to = Integer.MAX_VALUE)
         private int mSize = 1024;
 
         private boolean mShowWhitePoint = true;
         private boolean mClip = false;
+        private boolean mUcs = false;
 
         private final List<Pair<ColorSpace, Integer>> mColorSpaces = new ArrayList<>(2);
         private final List<Point> mPoints = new ArrayList<>(0);
@@ -3241,6 +3269,35 @@
         }
 
         /**
+         * <p>Defines whether the chromaticity diagram should use the uniform
+         * chromaticity scale (CIE 1976 UCS). When the uniform chromaticity scale
+         * is used, the distance between two points on the diagram is approximately
+         * proportional to the perceived color difference.</p>
+         *
+         * <p>The following code snippet shows how to enable the uniform chromaticity
+         * scale. The image below shows the result:</p>
+         * <pre class="prettyprint">
+         * Bitmap bitmap = ColorSpace.createRenderer()
+         *     .uniformChromaticityScale(true)
+         *     .add(ColorSpace.get(ColorSpace.Named.SRGB), 0xffffffff)
+         *     .add(ColorSpace.get(ColorSpace.Named.DCI_P3), 0xffffc845)
+         *     .render();
+         * </pre>
+         * <p>
+         *     <img src="{@docRoot}reference/android/images/graphics/colorspace_ucs.png" />
+         *     <figcaption style="text-align: center;">CIE 1976 UCS diagram</figcaption>
+         * </p>
+         *
+         * @param ucs True to render the chromaticity diagram as the CIE 1976 UCS diagram
+         * @return This instance of {@link Renderer}
+         */
+        @NonNull
+        public Renderer uniformChromaticityScale(boolean ucs) {
+            mUcs = ucs;
+            return this;
+        }
+
+        /**
          * Sets the dimensions (width and height) in pixels of the output bitmap.
          * The size must be at least 128px and defaults to 1024px.
          *
@@ -3302,7 +3359,7 @@
          * </pre>
          * <p>
          *     <img src="{@docRoot}reference/android/images/graphics/colorspace_comparison2.png" />
-         *     <figcaption style="text-align: center;">sRGB vs DCI-P3</figcaption>
+         *     <figcaption style="text-align: center;">sRGB, DCI-P3, ACES and scRGB</figcaption>
          * </p>
          *
          * @param colorSpace The color space whose gamut to render on the diagram
@@ -3385,6 +3442,7 @@
 
             setTransform(canvas, width, height, primaries);
             drawBox(canvas, width, height, paint, path);
+            setUcsTransform(canvas, height);
             drawLocus(canvas, width, height, paint, path, primaries);
             drawGamuts(canvas, width, height, paint, path, primaries, whitePoint);
             drawPoints(canvas, width, height, paint);
@@ -3406,7 +3464,11 @@
 
             paint.setStyle(Paint.Style.FILL);
 
+            float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f);
+
             float[] v = new float[3];
+            float[] xy = new float[2];
+
             for (final Point point : mPoints) {
                 v[0] = point.mRgb[0];
                 v[1] = point.mRgb[1];
@@ -3415,10 +3477,13 @@
 
                 paint.setColor(point.mColor);
 
-                // XYZ to xyY, assuming Y=1.0
+                // XYZ to xyY, assuming Y=1.0, then to L*u*v* if needed
                 float sum = v[0] + v[1] + v[2];
-                canvas.drawCircle(width * v[0] / sum, height - height * v[1] / sum,
-                        4.0f, paint);
+                xy[0] = v[0] / sum;
+                xy[1] = v[1] / sum;
+                if (mUcs) xyYToUv(xy);
+
+                canvas.drawCircle(width * xy[0], height - height * xy[1], radius, paint);
             }
         }
 
@@ -3440,6 +3505,8 @@
                 @NonNull Paint paint, @NonNull Path path,
                 @NonNull @Size(6) float[] primaries, @NonNull @Size(2) float[] whitePoint) {
 
+            float radius = 4.0f / (mUcs ? UCS_SCALE : 1.0f);
+
             for (final Pair<ColorSpace, Integer> item : mColorSpaces) {
                 ColorSpace colorSpace = item.first;
                 int color = item.second;
@@ -3447,7 +3514,7 @@
                 if (colorSpace.getModel() != Model.RGB) continue;
 
                 Rgb rgb = (Rgb) colorSpace;
-                getPrimaries(rgb, primaries);
+                getPrimaries(rgb, primaries, mUcs);
 
                 path.rewind();
                 path.moveTo(width * primaries[0], height - height * primaries[1]);
@@ -3462,11 +3529,12 @@
                 // Draw the white point
                 if (mShowWhitePoint) {
                     rgb.getWhitePoint(whitePoint);
+                    if (mUcs) xyYToUv(whitePoint);
 
                     paint.setStyle(Paint.Style.FILL);
                     paint.setColor(color);
-                    canvas.drawCircle(width * whitePoint[0], height - height * whitePoint[1],
-                            4.0f, paint);
+                    canvas.drawCircle(
+                            width * whitePoint[0], height - height * whitePoint[1], radius, paint);
                 }
             }
         }
@@ -3477,10 +3545,12 @@
          *
          * @param rgb The color space whose primaries to extract
          * @param primaries A pre-allocated array of 6 floats that will hold the result
+         * @param asUcs True if the primaries should be returned in Luv, false for xyY
          */
         @NonNull
         @Size(6)
-        private static float[] getPrimaries(@NonNull Rgb rgb, @NonNull @Size(6) float[] primaries) {
+        private static float[] getPrimaries(@NonNull Rgb rgb,
+                @NonNull @Size(6) float[] primaries, boolean asUcs) {
             // TODO: We should find a better way to handle these cases
             if (rgb.equals(ColorSpace.get(Named.EXTENDED_SRGB)) ||
                     rgb.equals(ColorSpace.get(Named.LINEAR_EXTENDED_SRGB))) {
@@ -3490,9 +3560,11 @@
                 primaries[3] = 1.24f;
                 primaries[4] = -0.23f;
                 primaries[5] = -0.57f;
-                return primaries;
+            } else {
+                rgb.getPrimaries(primaries);
             }
-            return rgb.getPrimaries(primaries);
+            if (asUcs) xyYToUv(primaries);
+            return primaries;
         }
 
         /**
@@ -3513,7 +3585,13 @@
             int vertexCount = SPECTRUM_LOCUS_X.length * CHROMATICITY_RESOLUTION * 6;
             float[] vertices = new float[vertexCount * 2];
             int[] colors = new int[vertices.length];
-            computeChromaticityMesh(NATIVE_SIZE, NATIVE_SIZE, vertices, colors);
+            computeChromaticityMesh(vertices, colors);
+
+            if (mUcs) xyYToUv(vertices);
+            for (int i = 0; i < vertices.length; i += 2) {
+                vertices[i] *= width;
+                vertices[i + 1] = height - vertices[i + 1] * height;
+            }
 
             // Draw the spectral locus
             if (mClip && mColorSpaces.size() > 0) {
@@ -3522,7 +3600,8 @@
                     if (colorSpace.getModel() != Model.RGB) continue;
 
                     Rgb rgb = (Rgb) colorSpace;
-                    getPrimaries(rgb, primaries);
+                    getPrimaries(rgb, primaries, mUcs);
+
                     break;
                 }
 
@@ -3559,6 +3638,7 @@
             }
             path.close();
 
+            paint.setStrokeWidth(4.0f / (mUcs ? UCS_SCALE : 1.0f));
             paint.setStyle(Paint.Style.STROKE);
             paint.setColor(0xff000000);
             canvas.drawPath(path, paint);
@@ -3576,25 +3656,38 @@
          */
         private void drawBox(@NonNull Canvas canvas, int width, int height, @NonNull Paint paint,
                 @NonNull Path path) {
+
+            int lineCount = 10;
+            float scale = 1.0f;
+            if (mUcs) {
+                lineCount = 7;
+                scale = UCS_SCALE;
+            }
+
             // Draw the unit grid
             paint.setStyle(Paint.Style.STROKE);
             paint.setStrokeWidth(2.0f);
             paint.setColor(0xffc0c0c0);
-            for (int i = 1; i <= 9; i++) {
-                canvas.drawLine(0.0f, height - (height * i / 10.0f),
-                        0.9f * width, height - (height * i / 10.0f), paint);
-                canvas.drawLine(width * i / 10.0f, height,
-                        width * i / 10.0f, 0.1f * height, paint);
+
+            for (int i = 1; i < lineCount - 1; i++) {
+                float v = i / 10.0f;
+                float x = (width * v) * scale;
+                float y = height - (height * v) * scale;
+
+                canvas.drawLine(0.0f, y, 0.9f * width, y, paint);
+                canvas.drawLine(x, height, x, 0.1f * height, paint);
             }
 
             // Draw tick marks
             paint.setStrokeWidth(4.0f);
             paint.setColor(0xff000000);
-            for (int i = 1; i <= 9; i++) {
-                canvas.drawLine(0.0f, height - (height * i / 10.0f),
-                        width / 100.0f, height - (height * i / 10.0f), paint);
-                canvas.drawLine(width * i / 10.0f, height,
-                        width * i / 10.0f, height - (height / 100.0f), paint);
+            for (int i = 1; i < lineCount - 1; i++) {
+                float v = i / 10.0f;
+                float x = (width * v) * scale;
+                float y = height - (height * v) * scale;
+
+                canvas.drawLine(0.0f, y, width / 100.0f, y, paint);
+                canvas.drawLine(x, height, x, height - (height / 100.0f), paint);
             }
 
             // Draw the axis labels
@@ -3603,14 +3696,15 @@
             paint.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
 
             Rect bounds = new Rect();
-            for (int i = 1; i < 9; i++) {
+            for (int i = 1; i < lineCount - 1; i++) {
                 String text = "0." + i;
                 paint.getTextBounds(text, 0, text.length(), bounds);
 
-                float y = height - (height * i / 10.0f);
-                canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint);
+                float v = i / 10.0f;
+                float x = (width * v) * scale;
+                float y = height - (height * v) * scale;
 
-                float x = width * i / 10.0f;
+                canvas.drawText(text, -0.05f * width + 10, y + bounds.height() / 2.0f, paint);
                 canvas.drawText(text, x - bounds.width() / 2.0f,
                         height + bounds.height() + 16, paint);
             }
@@ -3643,7 +3737,7 @@
                 if (colorSpace.getModel() != Model.RGB) continue;
 
                 Rgb rgb = (Rgb) colorSpace;
-                getPrimaries(rgb, primaries);
+                getPrimaries(rgb, primaries, mUcs);
 
                 primariesBounds.left = Math.min(primariesBounds.left, primaries[4]);
                 primariesBounds.top = Math.min(primariesBounds.top, primaries[5]);
@@ -3651,26 +3745,42 @@
                 primariesBounds.bottom = Math.max(primariesBounds.bottom, primaries[3]);
             }
 
+            float max = mUcs ? 0.6f : 0.9f;
+
             primariesBounds.left = Math.min(0.0f, primariesBounds.left);
             primariesBounds.top = Math.min(0.0f, primariesBounds.top);
-            primariesBounds.right = Math.max(0.9f, primariesBounds.right);
-            primariesBounds.bottom = Math.max(0.9f, primariesBounds.bottom);
+            primariesBounds.right = Math.max(max, primariesBounds.right);
+            primariesBounds.bottom = Math.max(max, primariesBounds.bottom);
 
-            float scaleX = 0.9f / primariesBounds.width();
-            float scaleY = 0.9f / primariesBounds.height();
+            float scaleX = max / primariesBounds.width();
+            float scaleY = max / primariesBounds.height();
             float scale = Math.min(scaleX, scaleY);
 
             canvas.scale(mSize / (float) NATIVE_SIZE, mSize / (float) NATIVE_SIZE);
             canvas.scale(scale, scale);
             canvas.translate(
-                    (primariesBounds.width() - 0.9f) * width / 2.0f,
-                    (primariesBounds.height() - 0.9f) * height / 2.0f);
+                    (primariesBounds.width() - max) * width / 2.0f,
+                    (primariesBounds.height() - max) * height / 2.0f);
 
             // The spectrum extends ~0.85 vertically and ~0.65 horizontally
             // We shift the canvas a little bit to get nicer margins
             canvas.translate(0.05f * width, -0.05f * height);
         }
 
+        /**
+         * Computes and applies the Canvas transforms required to render the CIE
+         * 197 UCS chromaticity diagram.
+         *
+         * @param canvas The canvas to transform
+         * @param height Height in pixel of the final image
+         */
+        private void setUcsTransform(@NonNull Canvas canvas, int height) {
+            if (mUcs) {
+                canvas.translate(0.0f, (height - height * UCS_SCALE));
+                canvas.scale(UCS_SCALE, UCS_SCALE);
+            }
+        }
+
         // X coordinates of the spectral locus in CIE 1931
         private static final float[] SPECTRUM_LOCUS_X = {
                 0.175596f, 0.172787f, 0.170806f, 0.170085f, 0.160343f,
@@ -3716,21 +3826,15 @@
                 0.037799f, 0.029673f, 0.021547f, 0.013421f, 0.005295f
         };
 
-        // Number of subdivision of the inside of the spectral locus
-        private static final int CHROMATICITY_RESOLUTION = 32;
-        private static final double ONE_THIRD = 1.0 / 3.0;
-
         /**
          * Computes a 2D mesh representation of the CIE 1931 chromaticity
          * diagram.
          *
-         * @param width Width in pixels of the mesh
-         * @param height Height in pixels of the mesh
          * @param vertices Array of floats that will hold the mesh vertices
          * @param colors Array of floats that will hold the mesh colors
          */
-        private static void computeChromaticityMesh(int width, int height,
-                @NonNull float[] vertices, @NonNull int[] colors) {
+        private static void computeChromaticityMesh(@NonNull float[] vertices,
+                @NonNull int[] colors) {
 
             ColorSpace colorSpace = get(Named.SRGB);
 
@@ -3796,18 +3900,18 @@
                     colorIndex += 6;
 
                     // Flip the mesh upside down to match Canvas' coordinates system
-                    vertices[vertexIndex++] = v1x * width;
-                    vertices[vertexIndex++] = height - v1y * height;
-                    vertices[vertexIndex++] = v2x * width;
-                    vertices[vertexIndex++] = height - v2y * height;
-                    vertices[vertexIndex++] = v3x * width;
-                    vertices[vertexIndex++] = height - v3y * height;
-                    vertices[vertexIndex++] = v1x * width;
-                    vertices[vertexIndex++] = height - v1y * height;
-                    vertices[vertexIndex++] = v3x * width;
-                    vertices[vertexIndex++] = height - v3y * height;
-                    vertices[vertexIndex++] = v4x * width;
-                    vertices[vertexIndex++] = height - v4y * height;
+                    vertices[vertexIndex++] = v1x;
+                    vertices[vertexIndex++] = v1y;
+                    vertices[vertexIndex++] = v2x;
+                    vertices[vertexIndex++] = v2y;
+                    vertices[vertexIndex++] = v3x;
+                    vertices[vertexIndex++] = v3y;
+                    vertices[vertexIndex++] = v1x;
+                    vertices[vertexIndex++] = v1y;
+                    vertices[vertexIndex++] = v3x;
+                    vertices[vertexIndex++] = v3y;
+                    vertices[vertexIndex++] = v4x;
+                    vertices[vertexIndex++] = v4y;
                 }
             }
         }
diff --git a/graphics/java/android/graphics/PixelFormat.java b/graphics/java/android/graphics/PixelFormat.java
index 98082ca..c3a9443 100644
--- a/graphics/java/android/graphics/PixelFormat.java
+++ b/graphics/java/android/graphics/PixelFormat.java
@@ -22,13 +22,17 @@
 import java.lang.annotation.RetentionPolicy;
 
 public class PixelFormat {
-
     /** @hide */
     @IntDef({UNKNOWN, TRANSLUCENT, TRANSPARENT, OPAQUE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface Opacity {}
 
-    /* these constants need to match those in hardware/hardware.h */
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({RGBA_8888, RGBX_8888, RGBA_F16, RGB_888, RGB_565})
+    public @interface Format { };
+
+    // NOTE: these constants must match the values from graphics/common/x.x/types.hal
 
     public static final int UNKNOWN     = 0;
 
@@ -62,7 +66,6 @@
     @Deprecated
     public static final int RGB_332     = 0xB;
 
-
     /**
      * @deprecated use {@link android.graphics.ImageFormat#NV16
      * ImageFormat.NV16} instead.
@@ -84,6 +87,8 @@
     @Deprecated
     public static final int YCbCr_422_I = 0x14;
 
+    public static final int RGBA_F16    = 0x16;
+
     /**
      * @deprecated use {@link android.graphics.ImageFormat#JPEG
      * ImageFormat.JPEG} instead.
@@ -91,7 +96,10 @@
     @Deprecated
     public static final int JPEG        = 0x100;
 
-    public static void getPixelFormatInfo(int format, PixelFormat info) {
+    public int bytesPerPixel;
+    public int bitsPerPixel;
+
+    public static void getPixelFormatInfo(@Format int format, PixelFormat info) {
         switch (format) {
             case RGBA_8888:
             case RGBX_8888:
@@ -124,18 +132,23 @@
                 info.bitsPerPixel = 12;
                 info.bytesPerPixel = 1;
                 break;
+            case RGBA_F16:
+                info.bitsPerPixel = 64;
+                info.bytesPerPixel = 8;
+                break;
             default:
                 throw new IllegalArgumentException("unknown pixel format " + format);
         }
     }
 
-    public static boolean formatHasAlpha(int format) {
+    public static boolean formatHasAlpha(@Format int format) {
         switch (format) {
             case PixelFormat.A_8:
             case PixelFormat.LA_88:
             case PixelFormat.RGBA_4444:
             case PixelFormat.RGBA_5551:
             case PixelFormat.RGBA_8888:
+            case PixelFormat.RGBA_F16:
             case PixelFormat.TRANSLUCENT:
             case PixelFormat.TRANSPARENT:
                 return true;
@@ -143,9 +156,6 @@
         return false;
     }
 
-    public int  bytesPerPixel;
-    public int  bitsPerPixel;
-
     /**
      * Determine whether or not this is a public-visible and non-deprecated {@code format}.
      *
@@ -159,12 +169,13 @@
      *
      * @hide
      */
-    public static boolean isPublicFormat(int format) {
+    public static boolean isPublicFormat(@Format int format) {
         switch (format) {
             case RGBA_8888:
             case RGBX_8888:
             case RGB_888:
             case RGB_565:
+            case RGBA_F16:
                 return true;
         }
 
diff --git a/libs/hwui/OpenGLReadback.cpp b/libs/hwui/OpenGLReadback.cpp
index 4c8abc5..938b6ef 100644
--- a/libs/hwui/OpenGLReadback.cpp
+++ b/libs/hwui/OpenGLReadback.cpp
@@ -131,6 +131,13 @@
                 destWidth, destHeight, caches.maxTextureSize);
         return CopyResult::DestinationInvalid;
     }
+
+    // TODO: Add support for RGBA_F16 destinations
+    if (bitmap->colorType() == kRGBA_F16_SkColorType) {
+        ALOGW("Can't copy surface into bitmap, RGBA_F16 config is not supported");
+        return CopyResult::DestinationInvalid;
+    }
+
     GLuint fbo = renderState.createFramebuffer();
     if (!fbo) {
         ALOGW("Could not obtain an FBO");
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 5b5b74e..705395e 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -225,6 +225,12 @@
         *outInternalFormat = GL_LUMINANCE;
         *outType = GL_UNSIGNED_BYTE;
         break;
+    case kRGBA_F16_SkColorType:
+        // This format is always linear
+        *outFormat = GL_RGBA;
+        *outInternalFormat = GL_RGBA16F;
+        *outType = GL_HALF_FLOAT;
+        break;
     default:
         LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", colorType);
         break;
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 2177af1..a9058b1 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -108,6 +108,8 @@
         return PIXEL_FORMAT_RGBA_8888;
     case GL_RGB:
         return PIXEL_FORMAT_RGB_565;
+    case GL_RGBA16F:
+        return PIXEL_FORMAT_RGBA_FP16;
     default:
         LOG_ALWAYS_FATAL("Unsupported bitmap colorType: %d", internalFormat);
         return PIXEL_FORMAT_UNKNOWN;
@@ -306,7 +308,8 @@
 
 sk_sp<Bitmap> Bitmap::createFrom(sp<GraphicBuffer> graphicBuffer) {
     PixelFormat format = graphicBuffer->getPixelFormat();
-    if (!graphicBuffer.get() || format != PIXEL_FORMAT_RGBA_8888) {
+    if (!graphicBuffer.get() ||
+            (format != PIXEL_FORMAT_RGBA_8888 && format != PIXEL_FORMAT_RGBA_FP16)) {
         return nullptr;
     }
     SkImageInfo info = SkImageInfo::Make(graphicBuffer->getWidth(), graphicBuffer->getHeight(),
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 376346f..430d6be 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -100,7 +100,7 @@
             int saveCount = layerCanvas->save();
             SkASSERT(saveCount == 1);
 
-            layerCanvas->clipRect(layerDamage.toSkRect(), SkClipOp::kReplace);
+            layerCanvas->androidFramework_setDeviceClipRestriction(layerDamage.toSkIRect());
 
             auto savedLightCenter = mLightCenter;
             // map current light center into RenderNode's coordinate space
@@ -233,8 +233,8 @@
 void SkiaPipeline::renderFrameImpl(const LayerUpdateQueue& layers, const SkRect& clip,
         const std::vector<sp<RenderNode>>& nodes, bool opaque, const Rect &contentDrawBounds,
         SkCanvas* canvas) {
-
-    canvas->clipRect(clip, SkClipOp::kReplace);
+    SkAutoCanvasRestore saver(canvas, true);
+    canvas->androidFramework_setDeviceClipRestriction(clip.roundOut());
 
     if (!opaque) {
         canvas->clear(SK_ColorTRANSPARENT);
@@ -242,7 +242,6 @@
 
     if (1 == nodes.size()) {
         if (!nodes[0]->nothingToDraw()) {
-            SkAutoCanvasRestore acr(canvas, true);
             RenderNodeDrawable root(nodes[0].get(), canvas);
             root.draw(canvas);
         }
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 722faf6..f3a663e 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -324,11 +324,8 @@
         }
         void onDrawPaint(const SkPaint&) {
             EXPECT_EQ(0, mDrawCounter++);
-            //TODO: this unit test is failing on the commented check below, because of a missing
-            //feature. In Snapshot::applyClip HWUI is intersecting the clip with the clip root,
-            //even for kReplace_Op clips. We need to implement the same for Skia pipelines.
-            //EXPECT_EQ(SkRect::MakeLTRB(20, 10, 30, 40), TestUtils::getClipBounds(this)) //got instead 20 0 30 50
-            //        << "Expect resolved clip to be intersection of viewport clip and clip op";
+            EXPECT_EQ(SkRect::MakeLTRB(20, 10, 30, 40), TestUtils::getClipBounds(this))
+                    << "Expect resolved clip to be intersection of viewport clip and clip op";
         }
         int mDrawCounter = 0;
     };
diff --git a/libs/incident/Android.mk b/libs/incident/Android.mk
new file mode 100644
index 0000000..439e86d
--- /dev/null
+++ b/libs/incident/Android.mk
@@ -0,0 +1,41 @@
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libincident
+
+LOCAL_CFLAGS := \
+        -Wall -Werror -Wno-missing-field-initializers -Wno-unused-variable -Wunused-parameter
+
+LOCAL_SHARED_LIBRARIES := \
+        libbinder \
+        liblog \
+        libutils
+
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/../../core/java
+LOCAL_C_INCLUDES := \
+        $(LOCAL_PATH)/include
+
+LOCAL_SRC_FILES := \
+        ../../core/java/android/os/IIncidentManager.aidl \
+        ../../core/java/android/os/IIncidentReportCompletedListener.aidl \
+        ../../core/java/android/os/IIncidentReportStatusListener.aidl \
+        src/IncidentReportArgs.cpp
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include/android/os/IncidentReportArgs.h
new file mode 100644
index 0000000..956ef6c
--- /dev/null
+++ b/libs/incident/include/android/os/IncidentReportArgs.h
@@ -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.
+ */
+
+#ifndef ANDROID_OS_DUMPSTATE_ARGS_H_
+#define ANDROID_OS_DUMPSTATE_ARGS_H_
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <utils/String16.h>
+
+#include <set>
+#include <vector>
+
+namespace android {
+namespace os {
+
+using namespace std;
+
+class IncidentReportArgs : public Parcelable {
+public:
+    IncidentReportArgs();
+    explicit IncidentReportArgs(const IncidentReportArgs& that);
+    virtual ~IncidentReportArgs();
+
+    virtual status_t writeToParcel(Parcel* out) const;
+    virtual status_t readFromParcel(const Parcel* in);
+
+    void setAll(bool all);
+    void addSection(int section);
+    void addHeader(const vector<int8_t>& header);
+
+    inline bool all() const { return mAll; };
+    bool containsSection(int section) const;
+
+    inline const set<int>& sections() const { return mSections; }
+    inline const vector<vector<int8_t>>& headers() const { return mHeaders; }
+
+    void merge(const IncidentReportArgs& that);
+
+private:
+    set<int> mSections;
+    vector<vector<int8_t>> mHeaders;
+    bool mAll;
+};
+
+}
+}
+
+#endif // ANDROID_OS_DUMPSTATE_ARGS_H_
diff --git a/libs/incident/proto/android/privacy.proto b/libs/incident/proto/android/privacy.proto
new file mode 100644
index 0000000..ae5af0e
--- /dev/null
+++ b/libs/incident/proto/android/privacy.proto
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+option java_package = "android";
+option java_multiple_files = true;
+
+import "google/protobuf/descriptor.proto";
+
+package android;
+
+// TODO: It's better to track this by semantic types and set policy for those.
+// Do this for now to bootstrap the tools.
+enum Destination {
+    // Fields or messages annotated with DEST_LOCAL must never be
+    // extracted from the device automatically. They can be accessed
+    // by tools on the developer's workstation, and if they are sent
+    // to another device that must be by the user, with a PII warning. (TBD)
+    DEST_LOCAL = 0;
+
+    // Fields or messages annotated with DEST_EXPLICIT can be sent
+    // off the device with an explicit user action.
+    DEST_EXPLICIT = 1;
+
+    // Fields or messages annotated with DEST_LOCAL can be sent by
+    // automatic means, without per-sending user consent. The user
+    // still must have previously accepted a consent to share this
+    // information.
+    DEST_AUTOMATIC = 2;
+
+    // There is no more permissive option than DEST_AUTOMATIC.
+}
+
+message PrivacyFlags {
+  optional Destination dest = 1  [
+      default = DEST_LOCAL
+  ];
+}
+
+extend google.protobuf.FieldOptions {
+    // Flags for automatically filtering statistics
+    optional PrivacyFlags privacy = 102672883;
+}
diff --git a/libs/incident/src/IncidentReportArgs.cpp b/libs/incident/src/IncidentReportArgs.cpp
new file mode 100644
index 0000000..f604909
--- /dev/null
+++ b/libs/incident/src/IncidentReportArgs.cpp
@@ -0,0 +1,172 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "dumpstate"
+
+#include <android/os/IncidentReportArgs.h>
+
+#include <cutils/log.h>
+
+namespace android {
+namespace os {
+
+IncidentReportArgs::IncidentReportArgs()
+    :mSections(),
+     mAll(false)
+{
+}
+
+IncidentReportArgs::IncidentReportArgs(const IncidentReportArgs& that)
+    :mSections(that.mSections),
+     mHeaders(that.mHeaders),
+     mAll(that.mAll)
+{
+}
+
+IncidentReportArgs::~IncidentReportArgs()
+{
+}
+
+status_t
+IncidentReportArgs::writeToParcel(Parcel* out) const
+{
+    status_t err;
+
+    err = out->writeInt32(mAll);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    err = out->writeInt32(mSections.size());
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    for (set<int>::const_iterator it=mSections.begin(); it!=mSections.end(); it++) {
+        err = out->writeInt32(*it);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+
+    err = out->writeInt32(mHeaders.size());
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    for (vector<vector<int8_t>>::const_iterator it = mHeaders.begin(); it != mHeaders.end(); it++) {
+        err = out->writeByteVector(*it);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+
+    return NO_ERROR;
+}
+
+status_t
+IncidentReportArgs::readFromParcel(const Parcel* in)
+{
+    status_t err;
+
+    int32_t all;
+    err = in->readInt32(&all);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    if (all != 0) {
+        mAll = all;
+    }
+
+    mSections.clear();
+    int32_t sectionCount;
+    err = in->readInt32(&sectionCount);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    for (int i=0; i<sectionCount; i++) {
+        int32_t section;
+        err = in->readInt32(&section);
+        if (err != NO_ERROR) {
+            return err;
+        }
+
+        mSections.insert(section);
+    }
+
+    int32_t headerCount;
+    err = in->readInt32(&headerCount);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    mHeaders.resize(headerCount);
+    for (int i=0; i<headerCount; i++) {
+        err = in->readByteVector(&mHeaders[i]);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+
+    return OK;
+}
+
+void
+IncidentReportArgs::setAll(bool all)
+{
+    mAll = all;
+    if (all) {
+        mSections.clear();
+    }
+}
+
+void
+IncidentReportArgs::addSection(int section)
+{
+    if (!mAll) {
+        mSections.insert(section);
+    }
+}
+
+void
+IncidentReportArgs::addHeader(const vector<int8_t>& header)
+{
+    mHeaders.push_back(header);
+}
+
+bool
+IncidentReportArgs::containsSection(int section) const
+{
+     return mAll || mSections.find(section) != mSections.end();
+}
+
+void
+IncidentReportArgs::merge(const IncidentReportArgs& that)
+{
+    if (mAll) {
+        return;
+    } else if (that.mAll) {
+        mAll = true;
+        mSections.clear();
+    } else {
+        for (set<int>::const_iterator it=that.mSections.begin();
+                it!=that.mSections.end(); it++) {
+            mSections.insert(*it);
+        }
+    }
+}
+
+}
+}
diff --git a/libs/services/Android.mk b/libs/services/Android.mk
new file mode 100644
index 0000000..cbfd4b3
--- /dev/null
+++ b/libs/services/Android.mk
@@ -0,0 +1,43 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+# Provides C++ wrappers for system services.
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libservices
+LOCAL_SRC_FILES := \
+    ../../core/java/com/android/internal/os/IDropBoxManagerService.aidl \
+    src/os/DropBoxManager.cpp
+
+LOCAL_AIDL_INCLUDES := \
+    $(LOCAL_PATH)/../../core/java
+LOCAL_C_INCLUDES := \
+    system/core/include
+LOCAL_SHARED_LIBRARIES := \
+    libbinder \
+    liblog \
+    libcutils \
+    libutils
+
+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
+
+LOCAL_CFLAGS += -Wall -Werror -Wunused -Wunreachable-code
+
+include $(BUILD_SHARED_LIBRARY)
+
+
diff --git a/libs/services/include/android/os/DropBoxManager.h b/libs/services/include/android/os/DropBoxManager.h
new file mode 100644
index 0000000..8717178
--- /dev/null
+++ b/libs/services/include/android/os/DropBoxManager.h
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+#ifndef _ANDROID_OS_DROPBOXMANAGER_H
+#define _ANDROID_OS_DROPBOXMANAGER_H
+
+#include <android-base/unique_fd.h>
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/Status.h>
+#include <utils/RefBase.h>
+
+#include <vector>
+
+namespace android {
+namespace os {
+
+using namespace android;
+using namespace android::base;
+using namespace android::binder;
+using namespace std;
+
+class DropBoxManager : public virtual RefBase
+{
+public:
+    enum {
+        IS_EMPTY = 1,
+        IS_TEXT = 2,
+        IS_GZIPPED = 4
+    };
+
+    DropBoxManager();
+    virtual ~DropBoxManager();
+
+    static sp<DropBoxManager> create();
+
+    // Create a new entry with plain text contents.
+    Status addText(const String16& tag, const string& text);
+
+    // Create a new Entry with byte array contents. Makes a copy of the data.
+    Status addData(const String16& tag, uint8_t const* data, size_t size, int flags);
+
+    // Create a new Entry from a file. The file will be opened in this process
+    // and a handle will be passed to the system process, so no additional permissions
+    // are required from the system process.  Returns NULL if the file can't be opened.
+    Status addFile(const String16& tag, const string& filename, int flags);
+    
+    class Entry : public virtual RefBase, public Parcelable {
+    public:
+        Entry();
+        virtual ~Entry();
+
+        virtual status_t writeToParcel(Parcel* out) const;
+        virtual status_t readFromParcel(const Parcel* in);
+        
+    private:
+        Entry(const String16& tag, int32_t flags);
+        Entry(const String16& tag, int32_t flags, int fd);
+
+        String16 mTag;
+        int64_t mTimeMillis;
+        int32_t mFlags;
+
+        vector<uint8_t> mData;
+        unique_fd mFd;
+
+        friend class DropBoxManager;
+    };
+
+private:
+    enum {
+        HAS_BYTE_ARRAY = 8
+    };
+
+    Status add(const Entry& entry);
+};
+
+}} // namespace android::os
+
+#endif // _ANDROID_OS_DROPBOXMANAGER_H
+
diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp
new file mode 100644
index 0000000..bbb45f0
--- /dev/null
+++ b/libs/services/src/os/DropBoxManager.cpp
@@ -0,0 +1,199 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "DropBoxManager"
+
+#include <android/os/DropBoxManager.h>
+
+#include <binder/IServiceManager.h>
+#include <com/android/internal/os/IDropBoxManagerService.h>
+#include <cutils/log.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+namespace android {
+namespace os {
+
+using namespace ::com::android::internal::os;
+
+DropBoxManager::Entry::Entry()
+    :mTag(),
+     mTimeMillis(0),
+     mFlags(IS_EMPTY),
+     mData(),
+     mFd()
+{
+    mFlags = IS_EMPTY;
+}
+
+DropBoxManager::Entry::Entry(const String16& tag, int32_t flags)
+    :mTag(tag),
+     mTimeMillis(0),
+     mFlags(flags),
+     mData(),
+     mFd()
+{
+}
+
+DropBoxManager::Entry::Entry(const String16& tag, int32_t flags, int fd)
+    :mTag(tag),
+     mTimeMillis(0),
+     mFlags(flags),
+     mData(),
+     mFd(fd)
+{
+}
+
+DropBoxManager::Entry::~Entry()
+{
+}
+
+status_t
+DropBoxManager::Entry::writeToParcel(Parcel* out) const
+{
+    status_t err;
+
+    err = out->writeString16(mTag);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    err = out->writeInt64(mTimeMillis);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    if (mFd.get() != -1) {
+        err = out->writeInt32(mFlags & ~HAS_BYTE_ARRAY);  // Clear bit just to be safe
+        if (err != NO_ERROR) {
+            return err;
+        }
+        ALOGD("writing fd %d\n", mFd.get());
+        err = out->writeParcelFileDescriptor(mFd);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    } else {
+        err = out->writeInt32(mFlags | HAS_BYTE_ARRAY);
+        if (err != NO_ERROR) {
+            return err;
+        }
+        err = out->writeByteVector(mData);
+        if (err != NO_ERROR) {
+            return err;
+        }
+    }
+    return NO_ERROR;
+}
+
+status_t
+DropBoxManager::Entry::readFromParcel(const Parcel* in)
+{
+    status_t err;
+
+    err = in->readString16(&mTag);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    err = in->readInt64(&mTimeMillis);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    err = in->readInt32(&mFlags);
+    if (err != NO_ERROR) {
+        return err;
+    }
+
+    if ((mFlags & HAS_BYTE_ARRAY) != 0) {
+        err = in->readByteVector(&mData);
+        if (err != NO_ERROR) {
+            return err;
+        }
+        mFlags &= ~HAS_BYTE_ARRAY;
+    } else {
+        int fd;
+        fd = in->readParcelFileDescriptor();
+        if (fd == -1) {
+            return EBADF;
+        }
+        fd = dup(fd);
+        if (fd == -1) {
+            return errno;
+        }
+        mFd.reset(fd);
+    }
+
+    return NO_ERROR;
+}
+
+
+DropBoxManager::DropBoxManager()
+{
+}
+
+DropBoxManager::~DropBoxManager()
+{
+}
+
+Status
+DropBoxManager::addText(const String16& tag, const string& text)
+{
+    Entry entry(tag, IS_TEXT);
+    entry.mData.assign(text.c_str(), text.c_str() + text.size());
+    return add(entry);
+}
+
+Status
+DropBoxManager::addData(const String16& tag, uint8_t const* data,
+        size_t size, int flags)
+{
+    Entry entry(tag, flags);
+    entry.mData.assign(data, data+size);
+    return add(entry);
+}
+
+Status
+DropBoxManager::addFile(const String16& tag, const string& filename, int flags)
+{
+    int fd = open(filename.c_str(), O_RDONLY);
+    if (fd == -1) {
+        string message("addFile can't open file: ");
+        message += filename;
+        ALOGW("DropboxManager: %s", message.c_str());
+        return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, message.c_str());
+    }
+
+    Entry entry(tag, flags, fd);
+    return add(entry);
+}
+
+Status
+DropBoxManager::add(const Entry& entry)
+{
+    sp<IDropBoxManagerService> service = interface_cast<IDropBoxManagerService>(
+        defaultServiceManager()->getService(android::String16("dropbox")));
+    if (service == NULL) {
+        return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service");
+    }
+    return service->add(entry);
+}
+
+}} // namespace android::os
+
diff --git a/packages/SettingsLib/res/layout/settings_with_drawer.xml b/packages/SettingsLib/res/layout/settings_with_drawer.xml
index 1d08019..a68a44e 100644
--- a/packages/SettingsLib/res/layout/settings_with_drawer.xml
+++ b/packages/SettingsLib/res/layout/settings_with_drawer.xml
@@ -37,7 +37,8 @@
                 android:layout_height="wrap_content"
                 android:navigationContentDescription="@*android:string/action_bar_up_description"
                 android:theme="?android:attr/actionBarTheme"
-                style="?android:attr/toolbarStyle"/>
+                style="?android:attr/toolbarStyle"
+                android:background="?android:attr/colorPrimary" />
         </FrameLayout>
         <FrameLayout
             android:id="@+id/content_header_container"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d232e00..5c2787b 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -287,22 +287,6 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".recents.grid.RecentsGridActivity"
-                  android:label="@string/accessibility_desc_recent_apps"
-                  android:exported="false"
-                  android:launchMode="singleInstance"
-                  android:excludeFromRecents="true"
-                  android:stateNotNeeded="true"
-                  android:resumeWhilePausing="true"
-                  android:screenOrientation="behind"
-                  android:resizeableActivity="true"
-                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
-                  android:theme="@style/RecentsTheme.Grid">
-            <intent-filter>
-                <action android:name="com.android.systemui.recents.TOGGLE_RECENTS" />
-            </intent-filter>
-        </activity>
-
         <activity android:name=".recents.tv.RecentsTvActivity"
                   android:label="@string/accessibility_desc_recent_apps"
                   android:exported="false"
diff --git a/packages/SystemUI/res/drawable/header_dot.xml b/packages/SystemUI/res/drawable/header_dot.xml
index 568a9c2..dbc6b37 100644
--- a/packages/SystemUI/res/drawable/header_dot.xml
+++ b/packages/SystemUI/res/drawable/header_dot.xml
@@ -2,26 +2,27 @@
 <!--
 ** Copyright 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 
+** 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 
+**     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 
+** 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="oval">
+    android:shape="oval"
+    android:tint="?android:attr/colorControlNormal">
 
-    <solid 
+    <solid
         android:color="#FFFFFF"/>
 
-    <size 
+    <size
         android:width="3dp"
         android:height="3dp"/>
 </shape>
diff --git a/packages/SystemUI/res/layout-sw600dp/recents_grid.xml b/packages/SystemUI/res/layout-sw600dp/recents_grid.xml
deleted file mode 100644
index 1a054a5..0000000
--- a/packages/SystemUI/res/layout-sw600dp/recents_grid.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?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.
--->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:id="@+id/recents_view"
-    android:gravity="center"
-    android:background="#99000000">
-    <include layout="@layout/recents_stack_action_button" />
-</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_grid_task_view.xml b/packages/SystemUI/res/layout/recents_grid_task_view.xml
new file mode 100644
index 0000000..53bec70
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_grid_task_view.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<com.android.systemui.recents.views.grid.GridTaskView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:focusable="true">
+    <com.android.systemui.recents.views.TaskViewThumbnail
+        android:id="@+id/task_view_thumbnail"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+    <include layout="@layout/recents_task_view_header" />
+
+    <!-- TODO: Move this into a view stub -->
+    <include layout="@layout/recents_task_view_lock_to_app"/>
+
+    <!-- The incompatible app toast -->
+    <include layout="@layout/recents_task_view_incompatible_app_toast"/>
+</com.android.systemui.recents.views.grid.GridTaskView>
+
+
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index c8e5b61..015e4a2 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -4,9 +4,9 @@
      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.
@@ -26,35 +26,10 @@
     <include layout="@layout/recents_task_view_header" />
 
     <!-- TODO: Move this into a view stub -->
-    <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
-        android:id="@+id/lock_to_app_fab"
-        android:layout_width="@dimen/recents_lock_to_app_size"
-        android:layout_height="@dimen/recents_lock_to_app_size"
-        android:layout_gravity="bottom|end"
-        android:layout_marginEnd="15dp"
-        android:layout_marginBottom="15dp"
-        android:translationZ="4dp"
-        android:contentDescription="@string/recents_lock_to_app_button_label"
-        android:background="@drawable/recents_lock_to_task_button_bg"
-        android:visibility="invisible"
-        android:alpha="0">
-        <ImageView
-            android:layout_width="@dimen/recents_lock_to_app_icon_size"
-            android:layout_height="@dimen/recents_lock_to_app_icon_size"
-            android:layout_gravity="center"
-            android:src="@drawable/recents_lock_to_app_pin" />
-    </com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
+    <include layout="@layout/recents_task_view_lock_to_app"/>
 
     <!-- The incompatible app toast -->
-    <ViewStub android:id="@+id/incompatible_app_toast_stub"
-                android:inflatedId="@+id/incompatible_app_toast"
-                android:layout="@*android:layout/transient_notification"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="top|center_horizontal"
-                android:layout_marginTop="48dp"
-                android:layout_marginLeft="16dp"
-                android:layout_marginRight="16dp" />
+    <include layout="@layout/recents_task_view_incompatible_app_toast"/>
 </com.android.systemui.recents.views.TaskView>
 
 
diff --git a/packages/SystemUI/res/layout/recents_task_view_incompatible_app_toast.xml b/packages/SystemUI/res/layout/recents_task_view_incompatible_app_toast.xml
new file mode 100644
index 0000000..d573d6b
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_task_view_incompatible_app_toast.xml
@@ -0,0 +1,26 @@
+<?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.
+-->
+<ViewStub
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/incompatible_app_toast_stub"
+    android:inflatedId="@+id/incompatible_app_toast"
+    android:layout="@*android:layout/transient_notification"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="top|center_horizontal"
+    android:layout_marginTop="48dp"
+    android:layout_marginLeft="16dp"
+    android:layout_marginRight="16dp" />
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml b/packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml
new file mode 100644
index 0000000..8cece11
--- /dev/null
+++ b/packages/SystemUI/res/layout/recents_task_view_lock_to_app.xml
@@ -0,0 +1,34 @@
+<?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.
+-->
+<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+  xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/lock_to_app_fab"
+    android:layout_width="@dimen/recents_lock_to_app_size"
+    android:layout_height="@dimen/recents_lock_to_app_size"
+    android:layout_gravity="bottom|end"
+    android:layout_marginEnd="15dp"
+    android:layout_marginBottom="15dp"
+    android:translationZ="4dp"
+    android:contentDescription="@string/recents_lock_to_app_button_label"
+    android:background="@drawable/recents_lock_to_task_button_bg"
+    android:visibility="invisible"
+    android:alpha="0">
+    <ImageView
+        android:layout_width="@dimen/recents_lock_to_app_icon_size"
+        android:layout_height="@dimen/recents_lock_to_app_icon_size"
+        android:layout_gravity="center"
+        android:src="@drawable/recents_lock_to_app_pin" />
+</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index fe5606e..5f9ade5 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -720,18 +720,4 @@
     <!-- The size of a PIP menu action icon. -->
     <dimen name="pip_menu_action_icon_size">32dp</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/dimens_grid.xml b/packages/SystemUI/res/values/dimens_grid.xml
new file mode 100644
index 0000000..43e152a
--- /dev/null
+++ b/packages/SystemUI/res/values/dimens_grid.xml
@@ -0,0 +1,23 @@
+<?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.
+*/
+-->
+<resources>
+  <dimen name="recents_grid_padding_left_right">32dp</dimen>
+  <dimen name="recents_grid_padding_top_bottom">84dp</dimen>
+  <dimen name="recents_grid_padding_task_view">20dp</dimen>
+</resources>
+
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 08b8988..93b6965 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -44,22 +44,6 @@
         <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) -->
     <style name="RecentsTheme.NoWallpaper">
         <item name="android:windowBackground">@android:color/black</item>
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 13e047c..6a868d5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -218,7 +218,8 @@
                     Preconditions.checkState(mState == State.DOZE_REQUEST_PULSE);
                     break;
                 case DOZE_PULSE_DONE:
-                    Preconditions.checkState(mState == State.DOZE_PULSING);
+                    Preconditions.checkState(
+                            mState == State.DOZE_REQUEST_PULSE || mState == State.DOZE_PULSING);
                     break;
                 default:
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index 790f3f6..0265c9e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -57,7 +57,6 @@
 import com.android.systemui.recents.events.ui.RecentsDrawnEvent;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.grid.RecentsGridImpl;
 import com.android.systemui.recents.tv.RecentsTvImpl;
 import com.android.systemui.stackdivider.Divider;
 
@@ -84,7 +83,6 @@
     static {
         RECENTS_ACTIVITIES.add(RecentsImpl.RECENTS_ACTIVITY);
         RECENTS_ACTIVITIES.add(RecentsTvImpl.RECENTS_TV_ACTIVITY);
-        RECENTS_ACTIVITIES.add(RecentsGridImpl.RECENTS_MOSAIC_ACTIVITY);
     }
 
     // Purely for experimentation
@@ -207,8 +205,6 @@
                 getSystemService(Context.UI_MODE_SERVICE);
         if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
             mImpl = new RecentsTvImpl(mContext);
-        } else if (SystemProperties.getBoolean("ro.recents.grid", false) == true) {
-            mImpl = new RecentsGridImpl(mContext);
         } else {
             mImpl = new RecentsImpl(mContext);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 73c6e6e..711f0c6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -20,6 +20,7 @@
 import android.content.res.Resources;
 import android.graphics.Rect;
 
+import android.os.SystemProperties;
 import com.android.systemui.R;
 import com.android.systemui.recents.misc.SystemServicesProxy;
 
@@ -58,6 +59,10 @@
     public boolean fakeShadows;
     public int svelteLevel;
 
+    // Whether this product supports Grid-based Recents. If this is field is set to true, then
+    // Recents will layout task views in a grid mode when there's enough space in the screen.
+    public boolean isGridEnabled;
+
     public RecentsConfiguration(Context context) {
         // Load only resources that can not change after the first load either through developer
         // settings or via multi window
@@ -66,6 +71,7 @@
         Resources res = appContext.getResources();
         fakeShadows = res.getBoolean(R.bool.config_recents_fake_shadows);
         svelteLevel = res.getInteger(R.integer.recents_svelte_level);
+        isGridEnabled = SystemProperties.getBoolean("ro.recents.grid", false);
 
         float screenDensity = context.getResources().getDisplayMetrics().density;
         smallestWidth = ssp.getDeviceSmallestWidth();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java
deleted file mode 100644
index 71c2148..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridActivity.java
+++ /dev/null
@@ -1,504 +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 com.android.systemui.recents.grid;
-
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
-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.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.TextView;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
-import com.android.systemui.recents.RecentsActivity;
-import com.android.systemui.recents.RecentsActivityLaunchState;
-import com.android.systemui.recents.RecentsConfiguration;
-import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
-import com.android.systemui.recents.events.activity.HideRecentsEvent;
-import com.android.systemui.recents.events.activity.LaunchNextTaskRequestEvent;
-import com.android.systemui.recents.events.activity.LaunchTaskEvent;
-import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
-import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
-import com.android.systemui.recents.events.ui.AllTaskViewsDismissedEvent;
-import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
-import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
-import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
-import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
-import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.recents.misc.Utilities;
-import com.android.systemui.recents.model.RecentsTaskLoadPlan;
-import com.android.systemui.recents.model.RecentsTaskLoader;
-import com.android.systemui.recents.model.Task;
-import com.android.systemui.recents.model.TaskStack;
-import com.android.systemui.recents.views.TaskView;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * The main grid recents activity started by the RecentsImpl.
- */
-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 ArrayList<Task> mTasks = new ArrayList<>();
-    private ArrayList<TaskView> mTaskViews = new ArrayList<>();
-    private ArrayList<Rect> mTaskViewRects;
-    private FrameLayout mRecentsView;
-    private TextView mEmptyView;
-    private View mClearAllButton;
-    private int mLastDisplayOrientation = Configuration.ORIENTATION_UNDEFINED;
-    private int mLastDisplayDensity;
-    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) {
-        super.onCreate(savedInstanceState);
-        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 |
-                View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
-                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
-        getWindow().getAttributes().privateFlags |=
-                WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
-        mEmptyView = (TextView) mInflater.inflate(R.layout.recents_empty, mRecentsView, false);
-        mClearAllButton = findViewById(R.id.button);
-
-        FrameLayout.LayoutParams emptyViewLayoutParams = new FrameLayout.LayoutParams(
-            FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT);
-        emptyViewLayoutParams.gravity = Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL;
-        mEmptyView.setLayoutParams(emptyViewLayoutParams);
-        mRecentsView.addView(mEmptyView);
-
-        mClearAllButton.setVisibility(View.VISIBLE);
-        FrameLayout.LayoutParams lp =
-                (FrameLayout.LayoutParams) mClearAllButton.getLayoutParams();
-        lp.gravity = Gravity.END;
-
-        mClearAllButton.setOnClickListener(v -> {
-            EventBus.getDefault().send(new DismissAllTaskViewsEvent());
-        });
-
-        mRecentsView.setOnClickListener(v -> {
-            EventBus.getDefault().send(new HideRecentsEvent(
-                    false /* triggeredFromAltTab */, false /* triggeredFromHomeKey */));
-        });
-
-        EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY);
-    }
-
-    private TaskView createView() {
-        return (TaskView) mInflater.inflate(R.layout.recents_task_view, mRecentsView, false);
-    }
-
-    private void removeTaskViews() {
-        for (View taskView : mTaskViews) {
-            ViewGroup parent = (ViewGroup) taskView.getParent();
-            if (parent != null) {
-                parent.removeView(taskView);
-            }
-        }
-    }
-
-    private void clearTaskViews() {
-        removeTaskViews();
-        mTaskViews.clear();
-    }
-
-    private TaskView getChildViewForTask(Task task) {
-        for (TaskView tv : mTaskViews) {
-            if (tv.getTask() == task) {
-                return tv;
-            }
-        }
-        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);
-        mEmptyView.setVisibility(empty ? View.VISIBLE : View.INVISIBLE);
-        if (empty) {
-            mEmptyView.bringToFront();
-        }
-    }
-
-    private void updateModel() {
-        RecentsTaskLoader loader = Recents.getTaskLoader();
-        RecentsTaskLoadPlan plan = RecentsImpl.consumeInstanceLoadPlan();
-        if (plan == null) {
-            plan = loader.createLoadPlan(this);
-        }
-        RecentsConfiguration config = Recents.getConfiguration();
-        RecentsActivityLaunchState launchState = config.getLaunchState();
-        if (!plan.hasTasks()) {
-            loader.preloadTasks(plan, -1, !launchState.launchedFromHome);
-        }
-        mTaskStack = plan.getTaskStack();
-        RecentsTaskLoadPlan.Options loadOpts = new RecentsTaskLoadPlan.Options();
-        loadOpts.runningTaskId = launchState.launchedToTaskId;
-        loadOpts.numVisibleTasks = MAX_VISIBLE_TASKS;
-        loadOpts.numVisibleTaskThumbnails = MAX_VISIBLE_TASKS;
-        loader.loadTasks(this, plan, loadOpts);
-
-        mTasks = mTaskStack.getStackTasks();
-    }
-
-    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);
-            // 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();
-            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);
-        }
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, true));
-        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 onAnimationEnd(Animation animation) {
-                updateViews();
-            }
-
-            @Override
-            public void onAnimationRepeat(Animation animation) { }
-        }, true /* reverse */);
-    }
-
-    @Override
-    protected void onStop() {
-        super.onStop();
-        EventBus.getDefault().send(new RecentsVisibilityChangedEvent(this, false));
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        EventBus.getDefault().unregister(this);
-    }
-
-    @Override
-    public void onBackPressed() {
-        // Back behaves like the recents button so just trigger a toggle event.
-        EventBus.getDefault().send(new ToggleRecentsEvent());
-    }
-
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        // Notify of the config change.
-        Configuration newDeviceConfiguration = Utilities.getAppConfiguration(this);
-        mDisplayRect = Recents.getSystemServices().getDisplayRect();
-        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;
-        updateViews();
-    }
-
-    void dismissRecentsToHome() {
-        Intent startMain = new Intent(Intent.ACTION_MAIN);
-        startMain.addCategory(Intent.CATEGORY_HOME);
-        startActivity(startMain);
-    }
-
-    /** Launches the task that recents was launched from if possible. */
-    boolean launchPreviousTask() {
-        if (mRecentsView != null) {
-            Task task = mTaskStack.getLaunchTarget();
-            if (task != null) {
-                TaskView taskView = getChildViewForTask(task);
-                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
-                        INVALID_STACK_ID, false));
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /** Dismisses recents back to the launch target task. */
-    boolean dismissRecentsToLaunchTargetTaskOrHome() {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (ssp.isRecentsActivityVisible()) {
-            // If we can launch the task that Recents was launched from, do that, otherwise go home.
-            if (launchPreviousTask()) return true;
-            dismissRecentsToHome();
-        }
-        return false;
-    }
-
-    /**** EventBus events ****/
-
-    public final void onBusEvent(HideRecentsEvent event) {
-        if (event.triggeredFromAltTab) {
-            dismissRecentsToLaunchTargetTaskOrHome();
-        } else if (event.triggeredFromHomeKey) {
-            dismissRecentsToHome();
-        } else {
-            // Fall through tap on the background view but not on any of the tasks.
-            dismissRecentsToHome();
-        }
-    }
-
-    public final void onBusEvent(ToggleRecentsEvent event) {
-        dismissRecentsToLaunchTargetTaskOrHome();
-    }
-
-    public final void onBusEvent(DismissTaskViewEvent event) {
-        int taskIndex = mTaskViews.indexOf(event.taskView);
-        if (taskIndex != -1) {
-            mTasks.remove(taskIndex);
-            ((ViewGroup) event.taskView.getParent()).removeView(event.taskView);
-            mTaskViews.remove(taskIndex);
-            EventBus.getDefault().send(
-                    new TaskViewDismissedEvent(event.taskView.getTask(), event.taskView, null));
-        }
-    }
-
-    public final void onBusEvent(TaskViewDismissedEvent event) {
-        mRecentsView.announceForAccessibility(this.getString(
-            R.string.accessibility_recents_item_dismissed, event.task.title));
-        updateControlVisibility();
-
-        EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
-
-        MetricsLogger.action(this, MetricsEvent.OVERVIEW_DISMISS,
-                event.task.key.getComponent().toString());
-    }
-
-    public final void onBusEvent(DeleteTaskDataEvent event) {
-        // Remove any stored data from the loader.
-        RecentsTaskLoader loader = Recents.getTaskLoader();
-        loader.deleteTaskData(event.task, false);
-
-        // Remove the task from activity manager.
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        ssp.removeTask(event.task.key.id);
-    }
-
-    public final void onBusEvent(final DismissAllTaskViewsEvent event) {
-        // Keep track of the tasks which will have their data removed.
-        ArrayList<Task> tasks = new ArrayList<>(mTaskStack.getStackTasks());
-        mRecentsView.announceForAccessibility(this.getString(
-                R.string.accessibility_recents_all_items_dismissed));
-        mTaskStack.removeAllTasks();
-        for (int i = tasks.size() - 1; i >= 0; i--) {
-            EventBus.getDefault().send(new DeleteTaskDataEvent(tasks.get(i)));
-        }
-        mTasks = new ArrayList<>();
-        updateModel();
-        updateViews();
-
-        MetricsLogger.action(this, MetricsEvent.OVERVIEW_DISMISS_ALL);
-    }
-
-    public final void onBusEvent(AllTaskViewsDismissedEvent event) {
-        SystemServicesProxy ssp = Recents.getSystemServices();
-        if (!ssp.hasDockedTask()) {
-            dismissRecentsToHome();
-        }
-    }
-
-    public final void onBusEvent(LaunchNextTaskRequestEvent event) {
-        if (mTaskStack.getTaskCount() > 0) {
-            Task launchTask = mTaskStack.getNextLaunchTarget();
-            TaskView launchTaskView = getChildViewForTask(launchTask);
-            if (launchTaskView != null) {
-                EventBus.getDefault().send(new LaunchTaskEvent(launchTaskView,
-                        launchTask, null, INVALID_STACK_ID, false /* screenPinningRequested */));
-                MetricsLogger.action(this, MetricsEvent.OVERVIEW_LAUNCH_PREVIOUS_TASK,
-                        launchTask.key.getComponent().toString());
-                return;
-            }
-        }
-        // We couldn't find a matching task view, or there are no tasks. Just hide recents back
-        // to home.
-        EventBus.getDefault().send(new HideRecentsEvent(false, true));
-    }
-
-    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/RecentsGridImpl.java b/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridImpl.java
deleted file mode 100644
index 41acaa0..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/grid/RecentsGridImpl.java
+++ /dev/null
@@ -1,47 +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 com.android.systemui.recents.grid;
-
-import android.app.ActivityManager;
-import android.content.Context;
-import android.content.Intent;
-import android.os.UserHandle;
-
-import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.recents.events.EventBus;
-import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
-
-public class RecentsGridImpl extends RecentsImpl {
-    public static final String RECENTS_MOSAIC_ACTIVITY =
-            "com.android.systemui.recents.grid.RecentsGridActivity";
-
-    public RecentsGridImpl(Context context) {
-        super(context);
-    }
-
-    @Override
-    protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
-            boolean isHomeStackVisible, boolean animate, int growTarget) {
-        Intent intent = new Intent();
-        intent.setClassName(RECENTS_PACKAGE, RECENTS_MOSAIC_ACTIVITY);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
-                | Intent.FLAG_ACTIVITY_TASK_ON_HOME);
-
-        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
-        EventBus.getDefault().send(new RecentsActivityStartingEvent());
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java
deleted file mode 100644
index 648f2f0..0000000
--- a/packages/SystemUI/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithm.java
+++ /dev/null
@@ -1,157 +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 com.android.systemui.recents.grid;
-
-import android.graphics.Rect;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-class TaskGridLayoutAlgorithm {
-
-    public enum VerticalGravity {
-        START, END, CENTER
-    }
-
-    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, RecentsGridActivity.MAX_VISIBLE_TASKS);
-
-        if (count == 0) {
-            return rects;
-        }
-        if (count <= 3) {
-            // Base case: single line.
-            int taskWidth, taskHeight;
-            if (preCalculatedTile != null) {
-                taskWidth = preCalculatedTile.width();
-                taskHeight = preCalculatedTile.height();
-            } else {
-                // 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,
-                    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);
-        } else {
-            // Three lines.
-            int lineHeight = (containerHeight - 2 * padding) / 3;
-            int lineTaskCount = (int) Math.ceil((double) count / 3);
-            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 (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);
-            rects.addAll(rectsC);
-        }
-        return rects;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
index ea50d89..ddffea2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -74,6 +74,7 @@
 import android.view.WindowManager.KeyboardShortcutsReceiver;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
+import android.app.KeyguardManager;
 
 import com.android.internal.app.AssistUtils;
 import com.android.internal.os.BackgroundThread;
@@ -124,6 +125,7 @@
     AssistUtils mAssistUtils;
     WindowManager mWm;
     IWindowManager mIwm;
+    KeyguardManager mKgm;
     UserManager mUm;
     Display mDisplay;
     String mRecentsPackage;
@@ -212,6 +214,7 @@
         mAssistUtils = new AssistUtils(context);
         mWm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
         mIwm = WindowManagerGlobal.getWindowManagerService();
+        mKgm = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
         mUm = UserManager.get(context);
         mDisplay = mWm.getDefaultDisplay();
         mRecentsPackage = context.getPackageName();
@@ -862,6 +865,16 @@
         return label;
     }
 
+    /**
+     * Returns whether the provided {@param userId} is currently locked (and showing Keyguard).
+     */
+    public boolean isDeviceLocked(int userId) {
+        if (mKgm == null) {
+            return false;
+        }
+        return mKgm.isDeviceLocked(userId);
+    }
+
     /** Returns the package name of the home activity. */
     public String getHomeActivityPackageName() {
         if (mPm == null) return null;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
index 2c5c437..4349e30 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -86,7 +86,7 @@
     public static <T extends View> T findParent(View v, Class<T> parentClass) {
         ViewParent parent = v.getParent();
         while (parent != null) {
-            if (parent.getClass().equals(parentClass)) {
+            if (parentClass.isAssignableFrom(parent.getClass())) {
                 return (T) parent;
             }
             parent = parent.getParent();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 9b48e4d..5877440 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -28,6 +28,7 @@
 import android.os.UserManager;
 import android.util.ArraySet;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 
 import com.android.systemui.Prefs;
@@ -130,6 +131,7 @@
 
         SparseArray<Task.TaskKey> affiliatedTasks = new SparseArray<>();
         SparseIntArray affiliatedTaskCounts = new SparseIntArray();
+        SparseBooleanArray lockedUsers = new SparseBooleanArray();
         String dismissDescFormat = mContext.getString(
                 R.string.accessibility_recents_item_will_be_dismissed);
         String appInfoDescFormat = mContext.getString(
@@ -177,12 +179,17 @@
             int backgroundColor = loader.getActivityBackgroundColor(t.taskDescription);
             boolean isSystemApp = (info != null) &&
                     ((info.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0);
+            if (lockedUsers.indexOfKey(t.userId) < 0) {
+                lockedUsers.put(t.userId, Recents.getSystemServices().isDeviceLocked(t.userId));
+            }
+            boolean isLocked = lockedUsers.get(t.userId);
 
             // Add the task to the stack
             Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, icon,
                     thumbnail, title, titleDescription, dismissDescription, appInfoDescription,
                     activityColor, backgroundColor, isLaunchTarget, isStackTask, isSystemApp,
-                    t.isDockable, t.bounds, t.taskDescription, t.resizeMode, t.topActivity);
+                    t.isDockable, t.bounds, t.taskDescription, t.resizeMode, t.topActivity,
+                    isLocked);
 
             allTasks.add(task);
             affiliatedTaskCounts.put(taskKey.id, affiliatedTaskCounts.get(taskKey.id, 0) + 1);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 86a0315..53f713a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -189,6 +189,9 @@
     @ViewDebug.ExportedProperty(category="recents")
     public ComponentName topActivity;
 
+    @ViewDebug.ExportedProperty(category="recents")
+    public boolean isLocked;
+
     private ArrayList<TaskCallbacks> mCallbacks = new ArrayList<>();
 
     public Task() {
@@ -200,7 +203,7 @@
                 String appInfoDescription, int colorPrimary, int colorBackground,
                 boolean isLaunchTarget, boolean isStackTask, boolean isSystemApp,
                 boolean isDockable, Rect bounds, ActivityManager.TaskDescription taskDescription,
-                int resizeMode, ComponentName topActivity) {
+                int resizeMode, ComponentName topActivity, boolean isLocked) {
         boolean isInAffiliationGroup = (affiliationTaskId != key.id);
         boolean hasAffiliationGroupColor = isInAffiliationGroup && (affiliationColor != 0);
         this.key = key;
@@ -224,6 +227,7 @@
         this.isDockable = isDockable;
         this.resizeMode = resizeMode;
         this.topActivity = topActivity;
+        this.isLocked = isLocked;
     }
 
     /**
@@ -250,6 +254,7 @@
         this.isSystemApp = o.isSystemApp;
         this.isDockable = o.isDockable;
         this.resizeMode = o.resizeMode;
+        this.isLocked = o.isLocked;
         this.topActivity = o.topActivity;
     }
 
@@ -355,6 +360,9 @@
         if (isFreeformTask()) {
             writer.print(" freeform=Y");
         }
+        if (isLocked) {
+            writer.print(" locked=Y");
+        }
         writer.print(" "); writer.print(title);
         writer.println();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
index 253d06a..dba085e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/AnimateableViewBounds.java
@@ -30,17 +30,17 @@
     private static final float MIN_ALPHA = 0.1f;
     private static final float MAX_ALPHA = 0.8f;
 
-    View mSourceView;
+    protected View mSourceView;
     @ViewDebug.ExportedProperty(category="recents")
-    Rect mClipRect = new Rect();
+    protected Rect mClipRect = new Rect();
     @ViewDebug.ExportedProperty(category="recents")
-    Rect mClipBounds = new Rect();
+    protected Rect mClipBounds = new Rect();
     @ViewDebug.ExportedProperty(category="recents")
-    Rect mLastClipBounds = new Rect();
+    protected Rect mLastClipBounds = new Rect();
     @ViewDebug.ExportedProperty(category="recents")
-    int mCornerRadius;
+    protected int mCornerRadius;
     @ViewDebug.ExportedProperty(category="recents")
-    float mAlpha = 1f;
+    protected float mAlpha = 1f;
 
     public AnimateableViewBounds(View source, int cornerRadius) {
         mSourceView = source;
@@ -110,7 +110,7 @@
         return mClipRect.bottom;
     }
 
-    private void updateClipBounds() {
+    protected void updateClipBounds() {
         mClipBounds.set(Math.max(0, mClipRect.left), Math.max(0, mClipRect.top),
                 mSourceView.getWidth() - Math.max(0, mClipRect.right),
                 mSourceView.getHeight() - Math.max(0, mClipRect.bottom));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
index 493e618..c1f4c8a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -157,7 +157,7 @@
 
             // Get the current transform for the task, which will be used to position it offscreen
             stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
-                    null);
+                    null, mStackView.useGridLayout());
 
             if (hideTask) {
                 tv.setVisibility(View.INVISIBLE);
@@ -230,7 +230,7 @@
             // Get the current transform for the task, which will be updated to the final transform
             // to animate to depending on how recents was invoked
             stackLayout.getStackTransform(task, stackScroller.getStackScroll(), mTmpTransform,
-                    null);
+                    null, mStackView.useGridLayout());
 
             if (launchState.launchedFromApp && !launchState.launchedViaDockGesture) {
                 if (task.isLaunchTarget) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 571c0f6..3c97310 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -38,7 +38,7 @@
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
-
+import com.android.systemui.recents.views.grid.TaskGridLayoutAlgorithm;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -326,7 +326,7 @@
     @ViewDebug.ExportedProperty(category="recents")
     int mMinTranslationZ;
     @ViewDebug.ExportedProperty(category="recents")
-    int mMaxTranslationZ;
+    public int mMaxTranslationZ;
 
     // Optimization, allows for quick lookup of task -> index
     private SparseIntArray mTaskIndexMap = new SparseIntArray();
@@ -334,6 +334,7 @@
 
     // The freeform workspace layout
     FreeformWorkspaceLayoutAlgorithm mFreeformLayoutAlgorithm;
+    TaskGridLayoutAlgorithm mTaskGridLayoutAlgorithm;
 
     // The transform to place TaskViews at the front and back of the stack respectively
     TaskViewTransform mBackOfStackTransform = new TaskViewTransform();
@@ -344,6 +345,7 @@
         mContext = context;
         mCb = cb;
         mFreeformLayoutAlgorithm = new FreeformWorkspaceLayoutAlgorithm(context);
+        mTaskGridLayoutAlgorithm = new TaskGridLayoutAlgorithm(context);
         reloadOnConfigurationChange(context);
     }
 
@@ -377,6 +379,7 @@
                 R.dimen.recents_layout_initial_bottom_offset_tablet,
                 R.dimen.recents_layout_initial_bottom_offset_tablet);
         mFreeformLayoutAlgorithm.reloadOnConfigurationChange(context);
+        mTaskGridLayoutAlgorithm.reloadOnConfigurationChange(context);
         mMinMargin = res.getDimensionPixelSize(R.dimen.recents_layout_min_margin);
         mBaseTopMargin = getDimensionForDevice(context,
                 R.dimen.recents_layout_top_margin_phone,
@@ -470,6 +473,9 @@
 
             updateFrontBackTransforms();
         }
+
+        // Initialize the grid layout
+        mTaskGridLayoutAlgorithm.initialize(displayRect, windowRect);
     }
 
     /**
@@ -825,24 +831,30 @@
      * is what the view is measured and laid out with.
      */
     public TaskViewTransform getStackTransform(Task task, float stackScroll,
-            TaskViewTransform transformOut, TaskViewTransform frontTransform) {
+            TaskViewTransform transformOut, TaskViewTransform frontTransform,
+            boolean useGridLayout) {
         return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
-                false /* forceUpdate */, false /* ignoreTaskOverrides */);
+                false /* forceUpdate */, false /* ignoreTaskOverrides */, useGridLayout);
     }
 
     public TaskViewTransform getStackTransform(Task task, float stackScroll,
             TaskViewTransform transformOut, TaskViewTransform frontTransform,
-            boolean ignoreTaskOverrides) {
+            boolean ignoreTaskOverrides, boolean useGridLayout) {
         return getStackTransform(task, stackScroll, mFocusState, transformOut, frontTransform,
-                false /* forceUpdate */, ignoreTaskOverrides);
+                false /* forceUpdate */, ignoreTaskOverrides, useGridLayout);
     }
 
     public TaskViewTransform getStackTransform(Task task, float stackScroll, int focusState,
             TaskViewTransform transformOut, TaskViewTransform frontTransform, boolean forceUpdate,
-            boolean ignoreTaskOverrides) {
+            boolean ignoreTaskOverrides, boolean useGridLayout) {
         if (mFreeformLayoutAlgorithm.isTransformAvailable(task, this)) {
             mFreeformLayoutAlgorithm.getTransform(task, transformOut, this);
             return transformOut;
+        } else if (useGridLayout) {
+            int taskIndex = mTaskIndexMap.get(task.key.id);
+            int taskCount = mTaskIndexMap.size();
+            mTaskGridLayoutAlgorithm.getTransform(taskIndex, taskCount, transformOut, this);
+            return transformOut;
         } else {
             // Return early if we have an invalid index
             int nonOverrideTaskProgress = mTaskIndexMap.get(task.key.id, -1);
@@ -867,7 +879,7 @@
             Rect windowOverrideRect) {
         TaskViewTransform transform = getStackTransform(task, stackScroll, mFocusState,
                 transformOut, frontTransform, true /* forceUpdate */,
-                false /* ignoreTaskOverrides */);
+                false /* ignoreTaskOverrides */, false /* useGridLayout */);
         return transformToScreenCoordinates(transform, windowOverrideRect);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 8c94c35..57fde67 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -92,6 +92,7 @@
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
+import com.android.systemui.recents.views.grid.GridTaskView;
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -158,6 +159,7 @@
     private int mTaskCornerRadiusPx;
     private int mDividerSize;
     private int mStartTimerIndicatorDuration;
+    private boolean mDraggingOverDockState;
 
     @ViewDebug.ExportedProperty(category="recents")
     private boolean mTaskViewsClipDirty = true;
@@ -499,13 +501,13 @@
 
             // Calculate the current and (if necessary) the target transform for the task
             transform = mLayoutAlgorithm.getStackTransform(task, curStackScroll,
-                    taskTransforms.get(i), frontTransform, ignoreTaskOverrides);
+                    taskTransforms.get(i), frontTransform, ignoreTaskOverrides, useGridLayout());
             if (useTargetStackScroll && !transform.visible) {
                 // If we have a target stack scroll and the task is not currently visible, then we
                 // just update the transform at the new scroll
                 // TODO: Optimize this
-                transformAtTarget = mLayoutAlgorithm.getStackTransform(task,
-                        targetStackScroll, new TaskViewTransform(), frontTransformAtTarget);
+                transformAtTarget = mLayoutAlgorithm.getStackTransform(task, targetStackScroll,
+                    new TaskViewTransform(), frontTransformAtTarget, useGridLayout());
                 if (transformAtTarget.visible) {
                     transform.copyFrom(transformAtTarget);
                 }
@@ -736,7 +738,7 @@
             } else {
                 mLayoutAlgorithm.getStackTransform(task, mStackScroller.getStackScroll(),
                         focusState, transform, null, true /* forceUpdate */,
-                        false /* ignoreTaskOverrides */);
+                        false /* ignoreTaskOverrides */, useGridLayout());
             }
             transform.visible = true;
         }
@@ -753,7 +755,7 @@
             Task task = tasks.get(i);
             TaskViewTransform transform = transformsOut.get(i);
             mLayoutAlgorithm.getStackTransform(task, stackScroll, focusState, transform, null,
-                    true /* forceUpdate */, ignoreTaskOverrides);
+                    true /* forceUpdate */, ignoreTaskOverrides, useGridLayout());
             transform.visible = true;
         }
     }
@@ -782,6 +784,11 @@
      * Updates the clip for each of the task views from back to front.
      */
     private void clipTaskViews() {
+        // We never clip task views in grid layout
+        if (Recents.getConfiguration().isGridEnabled) {
+            return;
+        }
+
         // Update the clip on each task child
         List<TaskView> taskViews = getTaskViews();
         TaskView tmpTv = null;
@@ -1506,7 +1513,11 @@
 
     @Override
     public TaskView createView(Context context) {
-        return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
+        if (Recents.getConfiguration().isGridEnabled) {
+            return (GridTaskView) mInflater.inflate(R.layout.recents_grid_task_view, this, false);
+        } else {
+            return (TaskView) mInflater.inflate(R.layout.recents_task_view, this, false);
+        }
     }
 
     @Override
@@ -1830,7 +1841,7 @@
         // Enlarge the dragged view slightly
         float finalScale = event.taskView.getScaleX() * DRAG_SCALE_FACTOR;
         mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
-                mTmpTransform, null);
+                mTmpTransform, null, useGridLayout());
         mTmpTransform.scale = finalScale;
         mTmpTransform.translationZ = mLayoutAlgorithm.mMaxTranslationZ + 1;
         mTmpTransform.dimAlpha = 0f;
@@ -1851,6 +1862,7 @@
                 Interpolators.FAST_OUT_SLOW_IN);
         boolean ignoreTaskOverrides = false;
         if (event.dropTarget instanceof TaskStack.DockState) {
+            mDraggingOverDockState = true;
             // Calculate the new task stack bounds that matches the window size that Recents will
             // have after the drop
             final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
@@ -1870,6 +1882,7 @@
             updateLayoutAlgorithm(true /* boundScroll */);
             ignoreTaskOverrides = true;
         } else {
+            mDraggingOverDockState = false;
             // Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
             // task view, so add it back to the ignore set after updating the layout
             removeIgnoreTask(event.task);
@@ -1880,6 +1893,7 @@
     }
 
     public final void onBusEvent(final DragEndEvent event) {
+        mDraggingOverDockState = false;
         // We don't handle drops on the dock regions
         if (event.dropTarget instanceof TaskStack.DockState) {
             // However, we do need to reset the overrides, since the last state of this task stack
@@ -2119,6 +2133,20 @@
     }
 
     /**
+     * Check whether we should use the grid layout.
+     * We use the grid layout for Recents iff all the following is true:
+     *  1. Grid-mode is enabled.
+     *  2. The activity is not in multi-window mode.
+     *  3. The user is not dragging a task view over the dock state.
+     * @return True if we should use the grid layout.
+     */
+    public boolean useGridLayout() {
+        return Recents.getConfiguration().isGridEnabled
+            && !((RecentsActivity) mContext).isInMultiWindowMode()
+            && !mDraggingOverDockState;
+    }
+
+    /**
      * Reads current system flags related to accessibility and screen pinning.
      */
     private void readSystemFlags() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index de7def6..9b30515 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -176,8 +176,7 @@
         super(context, attrs, defStyleAttr, defStyleRes);
         RecentsConfiguration config = Recents.getConfiguration();
         Resources res = context.getResources();
-        mViewBounds = new AnimateableViewBounds(this, res.getDimensionPixelSize(
-                R.dimen.recents_task_view_shadow_rounded_corners_radius));
+        mViewBounds = createOutlineProvider();
         if (config.fakeShadows) {
             setBackground(new FakeShadowDrawable(res, config));
         }
@@ -207,6 +206,12 @@
         return mTask;
     }
 
+    /* Create an outline provider to clip and outline the view */
+    protected AnimateableViewBounds createOutlineProvider() {
+        return new AnimateableViewBounds(this, mContext.getResources().getDimensionPixelSize(
+            R.dimen.recents_task_view_shadow_rounded_corners_radius));
+    }
+
     /** Returns the view bounds. */
     AnimateableViewBounds getViewBounds() {
         return mViewBounds;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
index c46adf1..780cbca 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewThumbnail.java
@@ -67,6 +67,7 @@
     private float mDimAlpha;
     private Matrix mScaleMatrix = new Matrix();
     private Paint mDrawPaint = new Paint();
+    private Paint mLockedPaint = new Paint();
     private Paint mBgFillPaint = new Paint();
     private BitmapShader mBitmapShader;
     private LightingColorFilter mLightingColorFilter = new LightingColorFilter(0xffffffff, 0);
@@ -102,6 +103,7 @@
         mCornerRadius = getResources().getDimensionPixelSize(
                 R.dimen.recents_task_view_rounded_corners_radius);
         mBgFillPaint.setColor(Color.WHITE);
+        mLockedPaint.setColor(Color.WHITE);
         mFullscreenThumbnailScale = context.getResources().getFraction(
                 com.android.internal.R.fraction.thumbnail_fullscreen_scale, 1, 1);
     }
@@ -133,7 +135,11 @@
                 (int) (mThumbnailRect.width() * mThumbnailScale));
         int thumbnailHeight = Math.min(viewHeight,
                 (int) (mThumbnailRect.height() * mThumbnailScale));
-        if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
+
+        if (mTask.isLocked) {
+            canvas.drawRoundRect(0, 0, viewWidth, viewHeight, mCornerRadius, mCornerRadius,
+                    mLockedPaint);
+        } else if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
             int topOffset = mTaskBar != null
                     ? mTaskBar.getHeight() - mCornerRadius
                     : 0;
@@ -200,11 +206,13 @@
                 ColorMatrixColorFilter filter = new ColorMatrixColorFilter(TMP_FILTER_COLOR_MATRIX);
                 mDrawPaint.setColorFilter(filter);
                 mBgFillPaint.setColorFilter(filter);
+                mLockedPaint.setColorFilter(filter);
             } else {
                 mLightingColorFilter.setColorMultiply(Color.argb(255, mul, mul, mul));
                 mDrawPaint.setColorFilter(mLightingColorFilter);
                 mDrawPaint.setColor(0xFFffffff);
                 mBgFillPaint.setColorFilter(mLightingColorFilter);
+                mLockedPaint.setColorFilter(mLightingColorFilter);
             }
         } else {
             int grey = mul;
@@ -299,6 +307,7 @@
         if (t.colorBackground != 0) {
             mBgFillPaint.setColor(t.colorBackground);
         }
+        mLockedPaint.setColor(t.colorPrimary);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
new file mode 100644
index 0000000..a029478
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/AnimateableGridViewBounds.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views.grid;
+
+import android.view.View;
+import com.android.systemui.recents.views.AnimateableViewBounds;
+
+/* An outline provider for grid-based task views. */
+class AnimateableGridViewBounds extends AnimateableViewBounds {
+
+    public AnimateableGridViewBounds(View source, int cornerRadius) {
+        super(source, cornerRadius);
+    }
+
+    @Override
+    protected void updateClipBounds() {
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java
new file mode 100644
index 0000000..da14e2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/GridTaskView.java
@@ -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.
+ */
+
+package com.android.systemui.recents.views.grid;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import com.android.systemui.R;
+import com.android.systemui.recents.views.AnimateableViewBounds;
+import com.android.systemui.recents.views.TaskView;
+
+public class GridTaskView extends TaskView {
+    public GridTaskView(Context context) {
+        this(context, null);
+    }
+
+    public GridTaskView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public GridTaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    protected AnimateableViewBounds createOutlineProvider() {
+        return new AnimateableGridViewBounds(this, mContext.getResources().getDimensionPixelSize(
+            R.dimen.recents_task_view_shadow_rounded_corners_radius));
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
new file mode 100644
index 0000000..8d1bec8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/grid/TaskGridLayoutAlgorithm.java
@@ -0,0 +1,88 @@
+/*
+ * 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.recents.views.grid;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import com.android.systemui.R;
+import com.android.systemui.recents.misc.Utilities;
+import com.android.systemui.recents.views.TaskStackLayoutAlgorithm;
+import com.android.systemui.recents.views.TaskViewTransform;
+
+public class TaskGridLayoutAlgorithm  {
+
+    private int mPaddingLeftRight;
+    private int mPaddingTopBottom;
+    private int mPaddingTaskView;
+
+    private Rect mDisplayRect;
+    private Rect mWindowRect;
+
+    private Rect mTaskGridRect;
+
+    public TaskGridLayoutAlgorithm(Context context) {
+        reloadOnConfigurationChange(context);
+    }
+
+    public void reloadOnConfigurationChange(Context context) {
+        Resources res = context.getResources();
+        mPaddingLeftRight = res.getDimensionPixelSize(R.dimen.recents_grid_padding_left_right);
+        mPaddingTopBottom = res.getDimensionPixelSize(R.dimen.recents_grid_padding_top_bottom);
+        mPaddingTaskView = res.getDimensionPixelSize(R.dimen.recents_grid_padding_task_view);
+
+        mTaskGridRect = new Rect();
+    }
+
+    public TaskViewTransform getTransform(int taskIndex, int taskAmount,
+        TaskViewTransform transformOut, TaskStackLayoutAlgorithm stackLayout) {
+
+        int taskPerLine = taskAmount < 2 ? 1 : (
+            taskAmount < 5 ? 2 : 3);
+
+        int taskWidth = (mDisplayRect.width() - mPaddingLeftRight * 2
+            - mPaddingTaskView * (taskPerLine - 1)) / taskPerLine;
+        int taskHeight = taskWidth * mDisplayRect.height() / mDisplayRect.width();
+        mTaskGridRect.set(0, 0, taskWidth, taskHeight);
+
+        int xIndex = taskIndex % taskPerLine;
+        int yIndex = taskIndex / taskPerLine;
+        int x = mPaddingLeftRight + (taskWidth + mPaddingTaskView) * xIndex;
+        int y = mPaddingTopBottom + (taskHeight + mPaddingTaskView) * yIndex;
+        float z = stackLayout.mMaxTranslationZ;
+
+        float dimAlpha = 0f;
+        float viewOutlineAlpha = 0f;
+
+        // Fill out the transform
+        transformOut.scale = 1f;
+        transformOut.alpha = 1f;
+        transformOut.translationZ = z;
+        transformOut.dimAlpha = dimAlpha;
+        transformOut.viewOutlineAlpha = viewOutlineAlpha;
+        transformOut.rect.set(mTaskGridRect);
+        transformOut.rect.offset(x, y);
+        Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
+        transformOut.visible = true;
+        return transformOut;
+    }
+
+    public void initialize(Rect displayRect, Rect windowRect) {
+        mDisplayRect = displayRect;
+        mWindowRect = windowRect;
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 008580a..d1d7520 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -37,6 +37,7 @@
 
     @Before
     public void SysuiSetup() throws Exception {
+        System.setProperty("dexmaker.share_classloader", "true");
         mContext = new TestableContext(InstrumentationRegistry.getTargetContext(), this);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 8b99d72..c3948258 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -214,6 +214,16 @@
 
     @Test
     @UiThreadTest
+    public void testSuppressingPulse_doesntCrash() {
+        mMachine.requestState(INITIALIZED);
+
+        mMachine.requestState(DOZE);
+        mMachine.requestState(DOZE_REQUEST_PULSE);
+        mMachine.requestState(DOZE_PULSE_DONE);
+    }
+
+    @Test
+    @UiThreadTest
     public void testScreen_offInDoze() {
         mMachine.requestState(INITIALIZED);
 
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
deleted file mode 100644
index a9f811b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/grid/TaskGridLayoutAlgorithmTest.java
+++ /dev/null
@@ -1,120 +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 com.android.systemui.recents.grid;
-
-import android.graphics.Rect;
-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 {
-
-    private static final List<Integer> ZERO_MARGIN = TaskGridLayoutAlgorithm.ZERO_MARGIN;
-
-    @Test
-    public void testOneTile() {
-        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
-                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, 1.2f /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0);
-        assertEquals(2, rects.size());
-        for (Rect rect : rects) {
-            assertEquals(600, rect.width());
-            assertEquals(499, rect.height());
-        }
-    }
-
-    @Test
-    public void testTwoTilesLandscapeWithPadding() {
-        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
-                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);
-        assertEquals(595, rectA.width());
-        assertEquals(595, rectB.width());
-        assertEquals(605, rectB.left);
-    }
-
-    @Test
-    public void testTwoTilesPortrait() {
-        List<Rect> rects = TaskGridLayoutAlgorithm.getRectsForTaskCount(
-                2, 500, 1200, 1 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0);
-        assertEquals(2, rects.size());
-        for (Rect rect : rects) {
-            assertEquals(250, rect.width());
-            assertEquals(250, rect.height());
-        }
-    }
-
-    @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, 2.4f /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0);
-        assertEquals(4, rects.size());
-        for (Rect rect : rects) {
-            assertEquals(600, rect.width());
-            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, 2 /* screenRatio */, 0 /* padding */, ZERO_MARGIN, 0);
-        assertEquals(9, rects.size());
-        for (Rect rect : rects) {
-            assertEquals(400, rect.width());
-            assertEquals(200, rect.height());
-        }
-        Rect rectE = rects.get(4);
-        assertEquals(400, rectE.left);
-        assertEquals(200, rectE.top);
-        Rect rectI = rects.get(8);
-        assertEquals(800, rectI.left);
-        assertEquals(400, rectI.top);
-    }}
-
diff --git a/services/core/java/com/android/server/ConsumerIrService.java b/services/core/java/com/android/server/ConsumerIrService.java
index ea6812d..2ed6c77 100644
--- a/services/core/java/com/android/server/ConsumerIrService.java
+++ b/services/core/java/com/android/server/ConsumerIrService.java
@@ -29,13 +29,13 @@
 
     private static final int MAX_XMIT_TIME = 2000000; /* in microseconds */
 
-    private static native long halOpen();
-    private static native int halTransmit(long halObject, int carrierFrequency, int[] pattern);
-    private static native int[] halGetCarrierFrequencies(long halObject);
+    private static native boolean halOpen();
+    private static native int halTransmit(int carrierFrequency, int[] pattern);
+    private static native int[] halGetCarrierFrequencies();
 
     private final Context mContext;
     private final PowerManager.WakeLock mWakeLock;
-    private final long mNativeHal;
+    private final boolean mHasNativeHal;
     private final Object mHalLock = new Object();
 
     ConsumerIrService(Context context) {
@@ -45,23 +45,23 @@
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
         mWakeLock.setReferenceCounted(true);
 
-        mNativeHal = halOpen();
+        mHasNativeHal = halOpen();
         if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONSUMER_IR)) {
-            if (mNativeHal == 0) {
+            if (!mHasNativeHal) {
                 throw new RuntimeException("FEATURE_CONSUMER_IR present, but no IR HAL loaded!");
             }
-        } else if (mNativeHal != 0) {
+        } else if (mHasNativeHal) {
             throw new RuntimeException("IR HAL present, but FEATURE_CONSUMER_IR is not set!");
         }
     }
 
     @Override
     public boolean hasIrEmitter() {
-        return mNativeHal != 0;
+        return mHasNativeHal;
     }
 
     private void throwIfNoIrEmitter() {
-        if (mNativeHal == 0) {
+        if (!mHasNativeHal) {
             throw new UnsupportedOperationException("IR emitter not available");
         }
     }
@@ -91,7 +91,7 @@
 
         // Right now there is no mechanism to ensure fair queing of IR requests
         synchronized (mHalLock) {
-            int err = halTransmit(mNativeHal, carrierFrequency, pattern);
+            int err = halTransmit(carrierFrequency, pattern);
 
             if (err < 0) {
                 Slog.e(TAG, "Error transmitting: " + err);
@@ -109,7 +109,7 @@
         throwIfNoIrEmitter();
 
         synchronized(mHalLock) {
-            return halGetCarrierFrequencies(mNativeHal);
+            return halGetCarrierFrequencies();
         }
     }
 }
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index a1c3564..f712f12 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -16,7 +16,11 @@
 
 package com.android.server;
 
+import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
+import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
+
 import android.Manifest.permission;
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -25,27 +29,29 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.database.ContentObserver;
+import android.net.INetworkRecommendationProvider;
 import android.net.INetworkScoreCache;
 import android.net.INetworkScoreService;
 import android.net.NetworkKey;
-import android.net.NetworkScoreManager;
 import android.net.NetworkScorerAppManager;
 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
 import android.net.RecommendationRequest;
 import android.net.RecommendationResult;
 import android.net.ScoredNetwork;
+import android.net.Uri;
 import android.net.wifi.WifiConfiguration;
-import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.Settings;
-import android.text.TextUtils;
+import android.provider.Settings.Global;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.TimedRemoteCaller;
 
-import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
@@ -59,6 +65,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.TimeoutException;
 import java.util.function.Consumer;
 
 /**
@@ -67,17 +74,20 @@
  */
 public class NetworkScoreService extends INetworkScoreService.Stub {
     private static final String TAG = "NetworkScoreService";
-    private static final boolean DBG = false;
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final Context mContext;
     private final NetworkScorerAppManager mNetworkScorerAppManager;
+    private final RequestRecommendationCaller mRequestRecommendationCaller;
     @GuardedBy("mScoreCaches")
     private final Map<Integer, RemoteCallbackList<INetworkScoreCache>> mScoreCaches;
     /** Lock used to update mPackageMonitor when scorer package changes occur. */
     private final Object mPackageMonitorLock = new Object[0];
+    private final Object mServiceConnectionLock = new Object[0];
 
     @GuardedBy("mPackageMonitorLock")
     private NetworkScorerPackageMonitor mPackageMonitor;
+    @GuardedBy("mServiceConnectionLock")
     private ScoringServiceConnection mServiceConnection;
 
     private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
@@ -99,10 +109,10 @@
      * manages the service connection.
      */
     private class NetworkScorerPackageMonitor extends PackageMonitor {
-        final String mRegisteredPackage;
+        final List<String> mPackagesToWatch;
 
-        private NetworkScorerPackageMonitor(String mRegisteredPackage) {
-            this.mRegisteredPackage = mRegisteredPackage;
+        private NetworkScorerPackageMonitor(List<String> packagesToWatch) {
+            mPackagesToWatch = packagesToWatch;
         }
 
         @Override
@@ -136,7 +146,7 @@
         }
 
         private void evaluateBinding(String scorerPackageName, boolean forceUnbind) {
-            if (mRegisteredPackage.equals(scorerPackageName)) {
+            if (mPackagesToWatch.contains(scorerPackageName)) {
                 if (DBG) {
                     Log.d(TAG, "Evaluating binding for: " + scorerPackageName
                             + ", forceUnbind=" + forceUnbind);
@@ -146,13 +156,14 @@
                 if (activeScorer == null) {
                     // Package change has invalidated a scorer, this will also unbind any service
                     // connection.
-                    Log.i(TAG, "Package " + mRegisteredPackage +
-                            " is no longer valid, disabling scoring.");
-                    setScorerInternal(null);
-                } else if (activeScorer.mScoringServiceClassName == null) {
-                    // The scoring service is not available, make sure it's unbound.
+                    if (DBG) Log.d(TAG, "No active scorers available.");
                     unbindFromScoringServiceIfNeeded();
-                } else { // The scoring service changed in some way.
+                } else if (activeScorer.packageName.equals(scorerPackageName)) {
+                    if (DBG) {
+                        Log.d(TAG, "Possible change to the active scorer: "
+                            + activeScorer.packageName);
+                    }
+                    // The scoring service changed in some way.
                     if (forceUnbind) {
                         unbindFromScoringServiceIfNeeded();
                     }
@@ -162,6 +173,27 @@
         }
     }
 
+    /**
+     * Reevaluates the service binding when the Settings toggle is changed.
+     */
+    private class SettingsObserver extends ContentObserver {
+
+        public SettingsObserver() {
+            super(null /*handler*/);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            onChange(selfChange, null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            if (DBG) Log.d(TAG, String.format("onChange(%s, %s)", selfChange, uri));
+            bindToScoringServiceIfNeeded();
+        }
+    }
+
     public NetworkScoreService(Context context) {
       this(context, new NetworkScorerAppManager(context));
     }
@@ -176,24 +208,16 @@
         mContext.registerReceiverAsUser(
                 mUserIntentReceiver, UserHandle.SYSTEM, filter, null /* broadcastPermission*/,
                 null /* scheduler */);
+        // TODO(jjoslin): 12/15/16 - Make timeout configurable.
+        mRequestRecommendationCaller =
+            new RequestRecommendationCaller(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS);
     }
 
     /** Called when the system is ready to run third-party code but before it actually does so. */
     void systemReady() {
         if (DBG) Log.d(TAG, "systemReady");
-        ContentResolver cr = mContext.getContentResolver();
-        if (Settings.Global.getInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 0) == 0) {
-            // On first run, we try to initialize the scorer to the one configured at build time.
-            // This will be a no-op if the scorer isn't actually valid.
-            String defaultPackage = mContext.getResources().getString(
-                    R.string.config_defaultNetworkScorerPackageName);
-            if (!TextUtils.isEmpty(defaultPackage)) {
-                mNetworkScorerAppManager.setActiveScorer(defaultPackage);
-            }
-            Settings.Global.putInt(cr, Settings.Global.NETWORK_SCORING_PROVISIONED, 1);
-        }
-
         registerPackageMonitorIfNeeded();
+        registerRecommendationSettingObserverIfNeeded();
     }
 
     /** Called when the system is ready for us to start third-party code. */
@@ -207,29 +231,40 @@
         bindToScoringServiceIfNeeded();
     }
 
+    private void registerRecommendationSettingObserverIfNeeded() {
+        final List<String> providerPackages =
+            mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
+        if (!providerPackages.isEmpty()) {
+            final ContentResolver resolver = mContext.getContentResolver();
+            final Uri uri = Global.getUriFor(Global.NETWORK_RECOMMENDATIONS_ENABLED);
+            resolver.registerContentObserver(uri, false, new SettingsObserver());
+        }
+    }
+
     private void registerPackageMonitorIfNeeded() {
         if (DBG) Log.d(TAG, "registerPackageMonitorIfNeeded");
-        NetworkScorerAppData scorer = mNetworkScorerAppManager.getActiveScorer();
+        final List<String> providerPackages =
+            mNetworkScorerAppManager.getPotentialRecommendationProviderPackages();
         synchronized (mPackageMonitorLock) {
             // Unregister the current monitor if needed.
             if (mPackageMonitor != null) {
                 if (DBG) {
                     Log.d(TAG, "Unregistering package monitor for "
-                            + mPackageMonitor.mRegisteredPackage);
+                            + mPackageMonitor.mPackagesToWatch);
                 }
                 mPackageMonitor.unregister();
                 mPackageMonitor = null;
             }
 
-            // Create and register the monitor if a scorer is active.
-            if (scorer != null) {
-                mPackageMonitor = new NetworkScorerPackageMonitor(scorer.mPackageName);
+            // Create and register the monitor if there are packages that could be providers.
+            if (!providerPackages.isEmpty()) {
+                mPackageMonitor = new NetworkScorerPackageMonitor(providerPackages);
                 // TODO: Need to update when we support per-user scorers. http://b/23422763
                 mPackageMonitor.register(mContext, null /* thread */, UserHandle.SYSTEM,
                         false /* externalStorage */);
                 if (DBG) {
                     Log.d(TAG, "Registered package monitor for "
-                            + mPackageMonitor.mRegisteredPackage);
+                            + mPackageMonitor.mPackagesToWatch);
                 }
             }
         }
@@ -243,22 +278,24 @@
 
     private void bindToScoringServiceIfNeeded(NetworkScorerAppData scorerData) {
         if (DBG) Log.d(TAG, "bindToScoringServiceIfNeeded(" + scorerData + ")");
-        if (scorerData != null && scorerData.mScoringServiceClassName != null) {
-            ComponentName componentName =
-                    new ComponentName(scorerData.mPackageName, scorerData.mScoringServiceClassName);
-            // If we're connected to a different component then drop it.
-            if (mServiceConnection != null
-                    && !mServiceConnection.mComponentName.equals(componentName)) {
-                unbindFromScoringServiceIfNeeded();
-            }
+        if (scorerData != null && scorerData.recommendationServiceClassName != null) {
+            ComponentName componentName = new ComponentName(scorerData.packageName,
+                    scorerData.recommendationServiceClassName);
+            synchronized (mServiceConnectionLock) {
+                // If we're connected to a different component then drop it.
+                if (mServiceConnection != null
+                        && !mServiceConnection.mComponentName.equals(componentName)) {
+                    unbindFromScoringServiceIfNeeded();
+                }
 
-            // If we're not connected at all then create a new connection.
-            if (mServiceConnection == null) {
-                mServiceConnection = new ScoringServiceConnection(componentName);
-            }
+                // If we're not connected at all then create a new connection.
+                if (mServiceConnection == null) {
+                    mServiceConnection = new ScoringServiceConnection(componentName);
+                }
 
-            // Make sure the connection is connected (idempotent)
-            mServiceConnection.connect(mContext);
+                // Make sure the connection is connected (idempotent)
+                mServiceConnection.connect(mContext);
+            }
         } else { // otherwise make sure it isn't bound.
             unbindFromScoringServiceIfNeeded();
         }
@@ -266,10 +303,13 @@
 
     private void unbindFromScoringServiceIfNeeded() {
         if (DBG) Log.d(TAG, "unbindFromScoringServiceIfNeeded");
-        if (mServiceConnection != null) {
-            mServiceConnection.disconnect(mContext);
+        synchronized (mServiceConnectionLock) {
+            if (mServiceConnection != null) {
+                mServiceConnection.disconnect(mContext);
+            }
+            mServiceConnection = null;
         }
-        mServiceConnection = null;
+        clearInternal();
     }
 
     @Override
@@ -349,7 +389,8 @@
         // mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
         mContext.enforceCallingOrSelfPermission(permission.SCORE_NETWORKS, TAG);
 
-        return setScorerInternal(packageName);
+        // Scorers (recommendation providers) are selected and no longer set.
+        return false;
     }
 
     @Override
@@ -359,56 +400,13 @@
         if (mNetworkScorerAppManager.isCallerActiveScorer(getCallingUid()) ||
                 mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED) ==
                         PackageManager.PERMISSION_GRANTED) {
-            // The return value is discarded here because at this point, the call should always
-            // succeed. The only reason for failure is if the new package is not a valid scorer, but
-            // we're disabling scoring altogether here.
-            setScorerInternal(null /* packageName */);
+            // no-op for now but we could write to the setting if needed.
         } else {
             throw new SecurityException(
                     "Caller is neither the active scorer nor the scorer manager.");
         }
     }
 
-    /** Set the active scorer. Callers are responsible for checking permissions as appropriate. */
-    private boolean setScorerInternal(String packageName) {
-        if (DBG) Log.d(TAG, "setScorerInternal(" + packageName + ")");
-        long token = Binder.clearCallingIdentity();
-        try {
-            unbindFromScoringServiceIfNeeded();
-            // Preemptively clear scores even though the set operation could fail. We do this for
-            // safety as scores should never be compared across apps; in practice, Settings should
-            // only be allowing valid apps to be set as scorers, so failure here should be rare.
-            clearInternal();
-            // Get the scorer that is about to be replaced, if any, so we can notify it directly.
-            NetworkScorerAppData prevScorer = mNetworkScorerAppManager.getActiveScorer();
-            boolean result = mNetworkScorerAppManager.setActiveScorer(packageName);
-            // Unconditionally attempt to bind to the current scorer. If setActiveScorer() failed
-            // then we'll attempt to restore the previous binding (if any), otherwise an attempt
-            // will be made to bind to the new scorer.
-            bindToScoringServiceIfNeeded();
-            if (result) { // new scorer successfully set
-                registerPackageMonitorIfNeeded();
-
-                Intent intent = new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED);
-                if (prevScorer != null) { // Directly notify the old scorer.
-                    intent.setPackage(prevScorer.mPackageName);
-                    // TODO: Need to update when we support per-user scorers. http://b/23422763
-                    mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
-                }
-
-                if (packageName != null) { // Then notify the new scorer
-                    intent.putExtra(NetworkScoreManager.EXTRA_NEW_SCORER, packageName);
-                    intent.setPackage(packageName);
-                    // TODO: Need to update when we support per-user scorers. http://b/23422763
-                    mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
-                }
-            }
-            return result;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     /** Clear scores. Callers are responsible for checking permissions as appropriate. */
     private void clearInternal() {
         sendCallback(new Consumer<INetworkScoreCache>() {
@@ -464,7 +462,22 @@
 
     @Override
     public RecommendationResult requestRecommendation(RecommendationRequest request) {
-        // TODO(jjoslin): 11/25/16 - Update with real impl.
+        mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
+        throwIfCalledOnMainThread();
+        final INetworkRecommendationProvider provider = getRecommendationProvider();
+        if (provider != null) {
+            try {
+                return mRequestRecommendationCaller.getRecommendationResult(provider, request);
+            } catch (RemoteException | TimeoutException e) {
+                Log.w(TAG, "Failed to request a recommendation.", e);
+                // TODO(jjoslin): 12/15/16 - Keep track of failures.
+            }
+        }
+
+        if (DBG) {
+            Log.d(TAG, "Returning the default network recommendation.");
+        }
+
         WifiConfiguration selectedConfig = null;
         if (request != null) {
             selectedConfig = request.getCurrentSelectedConfig();
@@ -474,7 +487,19 @@
 
     @Override
     public boolean requestScores(NetworkKey[] networks) {
-        // TODO(jjoslin): 12/13/16 - Implement
+        mContext.enforceCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED, TAG);
+        final INetworkRecommendationProvider provider = getRecommendationProvider();
+        if (provider != null) {
+            try {
+                provider.requestScores(networks);
+                // TODO(jjoslin): 12/15/16 - Consider pushing null scores into the cache to prevent
+                // repeated requests for the same scores.
+                return true;
+            } catch (RemoteException e) {
+                Log.w(TAG, "Failed to request scores.", e);
+                // TODO(jjoslin): 12/15/16 - Keep track of failures.
+            }
+        }
         return false;
     }
 
@@ -486,7 +511,7 @@
             writer.println("Scoring is disabled.");
             return;
         }
-        writer.println("Current scorer: " + currentScorer.mPackageName);
+        writer.println("Current scorer: " + currentScorer.packageName);
 
         sendCallback(new Consumer<INetworkScoreCache>() {
             @Override
@@ -499,10 +524,12 @@
             }
         }, getScoreCacheLists());
 
-        if (mServiceConnection != null) {
-            mServiceConnection.dump(fd, writer, args);
-        } else {
-            writer.println("ScoringServiceConnection: null");
+        synchronized (mServiceConnectionLock) {
+            if (mServiceConnection != null) {
+                mServiceConnection.dump(fd, writer, args);
+            } else {
+                writer.println("ScoringServiceConnection: null");
+            }
         }
         writer.flush();
     }
@@ -535,10 +562,27 @@
         }
     }
 
+    private void throwIfCalledOnMainThread() {
+        if (Thread.currentThread() == mContext.getMainLooper().getThread()) {
+            throw new RuntimeException("Cannot invoke on the main thread");
+        }
+    }
+
+    @Nullable
+    private INetworkRecommendationProvider getRecommendationProvider() {
+        synchronized (mServiceConnectionLock) {
+            if (mServiceConnection != null) {
+                return mServiceConnection.getRecommendationProvider();
+            }
+        }
+        return null;
+    }
+
     private static class ScoringServiceConnection implements ServiceConnection {
         private final ComponentName mComponentName;
-        private boolean mBound = false;
-        private boolean mConnected = false;
+        private volatile boolean mBound = false;
+        private volatile boolean mConnected = false;
+        private volatile INetworkRecommendationProvider mRecommendationProvider;
 
         ScoringServiceConnection(ComponentName componentName) {
             mComponentName = componentName;
@@ -569,12 +613,19 @@
             } catch (RuntimeException e) {
                 Log.e(TAG, "Unbind failed.", e);
             }
+
+            mRecommendationProvider = null;
+        }
+
+        INetworkRecommendationProvider getRecommendationProvider() {
+            return mRecommendationProvider;
         }
 
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             if (DBG) Log.d(TAG, "ScoringServiceConnection: " + name.flattenToString());
             mConnected = true;
+            mRecommendationProvider = INetworkRecommendationProvider.Stub.asInterface(service);
         }
 
         @Override
@@ -583,6 +634,7 @@
                 Log.d(TAG, "ScoringServiceConnection, disconnected: " + name.flattenToString());
             }
             mConnected = false;
+            mRecommendationProvider = null;
         }
 
         public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -590,4 +642,43 @@
                     + ", connected: " + mConnected);
         }
     }
+
+    /**
+     * Executes the async requestRecommendation() call with a timeout.
+     */
+    private static final class RequestRecommendationCaller
+            extends TimedRemoteCaller<RecommendationResult> {
+        private final IRemoteCallback mCallback;
+
+        RequestRecommendationCaller(long callTimeoutMillis) {
+            super(callTimeoutMillis);
+            mCallback = new IRemoteCallback.Stub() {
+                @Override
+                public void sendResult(Bundle data) throws RemoteException {
+                    final RecommendationResult result =
+                            data.getParcelable(EXTRA_RECOMMENDATION_RESULT);
+                    final int sequence = data.getInt(EXTRA_SEQUENCE, -1);
+                    onRemoteMethodResult(result, sequence);
+                }
+            };
+        }
+
+        /**
+         * Runs the requestRecommendation() call on the given {@link INetworkRecommendationProvider}
+         * instance.
+         *
+         * @param target the {@link INetworkRecommendationProvider} to request a recommendation
+         *               from
+         * @param request the {@link RecommendationRequest} from the calling client
+         * @return a {@link RecommendationResult} from the provider
+         * @throws RemoteException if the call failed
+         * @throws TimeoutException if the call took longer than the set timeout
+         */
+        RecommendationResult getRecommendationResult(INetworkRecommendationProvider target,
+                RecommendationRequest request) throws RemoteException, TimeoutException {
+            final int sequence = onBeforeRemoteCall();
+            target.requestRecommendation(request, mCallback, sequence);
+            return getResultTimed(sequence);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3ec1a4a..eae4905 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -369,6 +369,7 @@
 import static com.android.server.am.ActivityStackSupervisor.FORCE_FOCUS;
 import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.am.ActivityStackSupervisor.RESTORE_FROM_RECENTS;
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
@@ -539,8 +540,6 @@
     private static final String INTENT_REMOTE_BUGREPORT_FINISHED =
             "android.intent.action.REMOTE_BUGREPORT_FINISHED";
 
-    // Used to indicate that a task is removed it should also be removed from recents.
-    private static final boolean REMOVE_FROM_RECENTS = true;
     // Used to indicate that an app transition should be animated.
     static final boolean ANIMATE = true;
 
@@ -3684,6 +3683,15 @@
                 mNativeDebuggingApp = null;
             }
 
+            String invokeWith = null;
+            if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
+                // Debuggable apps may include a wrapper script with their library directory.
+                String wrapperFileName = app.info.nativeLibraryDir + "/wrap.sh";
+                if (new File(wrapperFileName).exists()) {
+                    invokeWith = "/system/bin/logwrapper " + wrapperFileName;
+                }
+            }
+
             String requiredAbi = (abiOverride != null) ? abiOverride : app.info.primaryCpuAbi;
             if (requiredAbi == null) {
                 requiredAbi = Build.SUPPORTED_ABIS[0];
@@ -3710,12 +3718,12 @@
                 startResult = Process.startWebView(entryPoint,
                         app.processName, uid, uid, gids, debugFlags, mountExternal,
                         app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
-                        app.info.dataDir, entryPointArgs);
+                        app.info.dataDir, null, entryPointArgs);
             } else {
                 startResult = Process.start(entryPoint,
                         app.processName, uid, uid, gids, debugFlags, mountExternal,
                         app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
-                        app.info.dataDir, entryPointArgs);
+                        app.info.dataDir, invokeWith, entryPointArgs);
             }
             checkTime(startTime, "startProcess: returned from zygote!");
             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
@@ -4817,7 +4825,7 @@
                     // because we don't support returning them across task boundaries. Also, to
                     // keep backwards compatibility we remove the task from recents when finishing
                     // task with root activity.
-                    res = removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity);
+                    res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity);
                     if (!res) {
                         Slog.i(TAG, "Removing task failed to finish activity");
                     }
@@ -5462,7 +5470,7 @@
                             tr.getBaseIntent().getComponent().getPackageName();
                     if (tr.userId != userId) continue;
                     if (!taskPackageName.equals(packageName)) continue;
-                    removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS);
+                    mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS);
                 }
             }
 
@@ -9576,79 +9584,6 @@
         mWindowManager.executeAppTransition();
     }
 
-    private void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess,
-            boolean removeFromRecents) {
-        if (removeFromRecents) {
-            mRecentTasks.remove(tr);
-            tr.removedFromRecents();
-        }
-        ComponentName component = tr.getBaseIntent().getComponent();
-        if (component == null) {
-            Slog.w(TAG, "No component for base intent of task: " + tr);
-            return;
-        }
-
-        // Find any running services associated with this app and stop if needed.
-        mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent()));
-
-        if (!killProcess) {
-            return;
-        }
-
-        // Determine if the process(es) for this task should be killed.
-        final String pkg = component.getPackageName();
-        ArrayList<ProcessRecord> procsToKill = new ArrayList<>();
-        ArrayMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap();
-        for (int i = 0; i < pmap.size(); i++) {
-
-            SparseArray<ProcessRecord> uids = pmap.valueAt(i);
-            for (int j = 0; j < uids.size(); j++) {
-                ProcessRecord proc = uids.valueAt(j);
-                if (proc.userId != tr.userId) {
-                    // Don't kill process for a different user.
-                    continue;
-                }
-                if (proc == mHomeProcess) {
-                    // Don't kill the home process along with tasks from the same package.
-                    continue;
-                }
-                if (!proc.pkgList.containsKey(pkg)) {
-                    // Don't kill process that is not associated with this task.
-                    continue;
-                }
-
-                for (int k = 0; k < proc.activities.size(); k++) {
-                    TaskRecord otherTask = proc.activities.get(k).task;
-                    if (tr.taskId != otherTask.taskId && otherTask.inRecents) {
-                        // Don't kill process(es) that has an activity in a different task that is
-                        // also in recents.
-                        return;
-                    }
-                }
-
-                if (proc.foregroundServices) {
-                    // Don't kill process(es) with foreground service.
-                    return;
-                }
-
-                // Add process to kill list.
-                procsToKill.add(proc);
-            }
-        }
-
-        // Kill the running processes.
-        for (int i = 0; i < procsToKill.size(); i++) {
-            ProcessRecord pr = procsToKill.get(i);
-            if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND
-                    && pr.curReceivers.isEmpty()) {
-                pr.kill("remove task", true);
-            } else {
-                // We delay killing processes that are not in the background or running a receiver.
-                pr.waitingToKill = "remove task";
-            }
-        }
-    }
-
     private void removeTasksByPackageNameLocked(String packageName, int userId) {
         // Remove all tasks with activities in the specified package from the list of recent tasks
         for (int i = mRecentTasks.size() - 1; i >= 0; i--) {
@@ -9658,7 +9593,7 @@
             ComponentName cn = tr.intent.getComponent();
             if (cn != null && cn.getPackageName().equals(packageName)) {
                 // If the package name matches, remove the task.
-                removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
+                mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
             }
         }
     }
@@ -9676,35 +9611,11 @@
             final boolean sameComponent = cn != null && cn.getPackageName().equals(packageName)
                     && (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
             if (sameComponent) {
-                removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS);
+                mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, REMOVE_FROM_RECENTS);
             }
         }
     }
 
-    /**
-     * Removes the task with the specified task id.
-     *
-     * @param taskId Identifier of the task to be removed.
-     * @param killProcess Kill any process associated with the task if possible.
-     * @param removeFromRecents Whether to also remove the task from recents.
-     * @return Returns true if the given task was found and removed.
-     */
-    private boolean removeTaskByIdLocked(int taskId, boolean killProcess,
-            boolean removeFromRecents) {
-        final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(
-                taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
-        if (tr != null) {
-            tr.removeTaskActivitiesLocked();
-            cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
-            if (tr.isPersistable) {
-                notifyTaskPersisterLocked(null, true);
-            }
-            return true;
-        }
-        Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId);
-        return false;
-    }
-
     @Override
     public void removeStack(int stackId) {
         enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()");
@@ -9715,15 +9626,7 @@
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
-                final ActivityStack stack = mStackSupervisor.getStack(stackId);
-                if (stack == null) {
-                    return;
-                }
-                final ArrayList<TaskRecord> tasks = stack.getAllTasks();
-                for (int i = tasks.size() - 1; i >= 0; i--) {
-                    removeTaskByIdLocked(
-                            tasks.get(i).taskId, true /* killProcess */, REMOVE_FROM_RECENTS);
-                }
+                mStackSupervisor.removeStackLocked(stackId);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -9752,7 +9655,7 @@
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
-                return removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS);
+                return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -22511,7 +22414,8 @@
                 long origId = Binder.clearCallingIdentity();
                 try {
                     // We remove the task from recents to preserve backwards
-                    if (!removeTaskByIdLocked(mTaskId, false, REMOVE_FROM_RECENTS)) {
+                    if (!mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
+                            REMOVE_FROM_RECENTS)) {
                         throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                     }
                 } finally {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index fe261f1..b4b3465 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -249,6 +249,9 @@
     // Don't execute any calls to resume.
     static final boolean DEFER_RESUME = true;
 
+    // Used to indicate that a task is removed it should also be removed from recents.
+    static final boolean REMOVE_FROM_RECENTS = true;
+
     // Activity actions an app cannot start if it uses a permission which is not granted.
     private static final ArrayMap<String, String> ACTION_TO_RUNTIME_PERMISSION =
             new ArrayMap<>();
@@ -2183,7 +2186,8 @@
                         // Update the return-to to reflect where the pinned stack task was moved
                         // from so that we retain the stack that was previously visible if the
                         // pinned stack is recreated. See moveActivityToPinnedStackLocked().
-                        task.setTaskToReturnTo(getFocusedStack().getStackId() == HOME_STACK_ID
+                        final int focusedStackId = getFocusedStack().getStackId();
+                        task.setTaskToReturnTo(focusedStackId == HOME_STACK_ID || !onTop
                                 ? HOME_ACTIVITY_TYPE : APPLICATION_ACTIVITY_TYPE);
                     }
                     moveTaskToStackLocked(tasks.get(i).taskId,
@@ -2374,6 +2378,141 @@
         return activityContainer.mStack;
     }
 
+    /**
+     * Removes the stack associed with the given {@param stackId}.  If the {@param stackId} is the
+     * pinned stack, then its tasks are not explicitly removed when the stack is destroyed, but
+     * instead moved back onto the fullscreen stack.
+     */
+    void removeStackLocked(int stackId) {
+        final ActivityStack stack = getStack(stackId);
+        if (stack == null) {
+            return;
+        }
+
+        final ArrayList<TaskRecord> tasks = stack.getAllTasks();
+        if (stack.getStackId() == PINNED_STACK_ID) {
+            final ActivityStack fullscreenStack = getStack(FULLSCREEN_WORKSPACE_STACK_ID);
+            if (fullscreenStack != null) {
+                final boolean isFullscreenStackVisible =
+                        fullscreenStack.getStackVisibilityLocked(null) == STACK_VISIBLE;
+                for (int i = 0; i < tasks.size(); i++) {
+                    // Insert the task either at the top of the fullscreen stack if it is hidden,
+                    // or just under the top task if it is currently visible
+                    final int insertPosition = isFullscreenStackVisible
+                            ? Math.max(0, fullscreenStack.getChildCount() - 1)
+                            : fullscreenStack.getChildCount();
+                    positionTaskInStackLocked(tasks.get(i).taskId, FULLSCREEN_WORKSPACE_STACK_ID,
+                            insertPosition);
+                }
+                ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
+                resumeFocusedStackTopActivityLocked();
+            } else {
+                // If there is no fullscreen stack, then create the stack and move all the tasks
+                // onto the stack
+                moveTasksToFullscreenStackLocked(PINNED_STACK_ID, false /* onTop */);
+            }
+        } else {
+            for (int i = tasks.size() - 1; i >= 0; i--) {
+                removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
+                        REMOVE_FROM_RECENTS);
+            }
+        }
+    }
+
+    /**
+     * Removes the task with the specified task id.
+     *
+     * @param taskId Identifier of the task to be removed.
+     * @param killProcess Kill any process associated with the task if possible.
+     * @param removeFromRecents Whether to also remove the task from recents.
+     * @return Returns true if the given task was found and removed.
+     */
+    boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) {
+        final TaskRecord tr = anyTaskForIdLocked(taskId, !RESTORE_FROM_RECENTS, INVALID_STACK_ID);
+        if (tr != null) {
+            tr.removeTaskActivitiesLocked();
+            cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
+            if (tr.isPersistable) {
+                mService.notifyTaskPersisterLocked(null, true);
+            }
+            return true;
+        }
+        Slog.w(TAG, "Request to remove task ignored for non-existent task " + taskId);
+        return false;
+    }
+
+    void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, boolean removeFromRecents) {
+        if (removeFromRecents) {
+            mRecentTasks.remove(tr);
+            tr.removedFromRecents();
+        }
+        ComponentName component = tr.getBaseIntent().getComponent();
+        if (component == null) {
+            Slog.w(TAG, "No component for base intent of task: " + tr);
+            return;
+        }
+
+        // Find any running services associated with this app and stop if needed.
+        mService.mServices.cleanUpRemovedTaskLocked(tr, component, new Intent(tr.getBaseIntent()));
+
+        if (!killProcess) {
+            return;
+        }
+
+        // Determine if the process(es) for this task should be killed.
+        final String pkg = component.getPackageName();
+        ArrayList<ProcessRecord> procsToKill = new ArrayList<>();
+        ArrayMap<String, SparseArray<ProcessRecord>> pmap = mService.mProcessNames.getMap();
+        for (int i = 0; i < pmap.size(); i++) {
+
+            SparseArray<ProcessRecord> uids = pmap.valueAt(i);
+            for (int j = 0; j < uids.size(); j++) {
+                ProcessRecord proc = uids.valueAt(j);
+                if (proc.userId != tr.userId) {
+                    // Don't kill process for a different user.
+                    continue;
+                }
+                if (proc == mService.mHomeProcess) {
+                    // Don't kill the home process along with tasks from the same package.
+                    continue;
+                }
+                if (!proc.pkgList.containsKey(pkg)) {
+                    // Don't kill process that is not associated with this task.
+                    continue;
+                }
+
+                for (int k = 0; k < proc.activities.size(); k++) {
+                    TaskRecord otherTask = proc.activities.get(k).task;
+                    if (tr.taskId != otherTask.taskId && otherTask.inRecents) {
+                        // Don't kill process(es) that has an activity in a different task that is
+                        // also in recents.
+                        return;
+                    }
+                }
+
+                if (proc.foregroundServices) {
+                    // Don't kill process(es) with foreground service.
+                    return;
+                }
+
+                // Add process to kill list.
+                procsToKill.add(proc);
+            }
+        }
+
+        // Kill the running processes.
+        for (int i = 0; i < procsToKill.size(); i++) {
+            ProcessRecord pr = procsToKill.get(i);
+            if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+                    && pr.curReceivers.isEmpty()) {
+                pr.kill("remove task", true);
+            } else {
+                // We delay killing processes that are not in the background or running a receiver.
+                pr.waitingToKill = "remove task";
+            }
+        }
+    }
+
     int getNextStackId() {
         while (true) {
             if (mNextFreeStackId >= FIRST_DYNAMIC_STACK_ID
diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java
index 132967c..3f0ebf2 100644
--- a/services/core/java/com/android/server/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java
@@ -46,7 +46,11 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.service.fingerprint.FingerprintActionStatsProto;
+import android.service.fingerprint.FingerprintServiceDumpProto;
+import android.service.fingerprint.FingerprintUserStatsProto;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.logging.MetricsLogger;
@@ -968,7 +972,11 @@
 
             final long ident = Binder.clearCallingIdentity();
             try {
-                dumpInternal(pw);
+                if (args.length > 0 && "--proto".equals(args[0])) {
+                    dumpProto(fd);
+                } else {
+                    dumpInternal(pw);
+                }
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1027,6 +1035,45 @@
         pw.println(dump);
     }
 
+    private void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+        for (UserInfo user : UserManager.get(getContext()).getUsers()) {
+            final int userId = user.getUserHandle().getIdentifier();
+
+            final long userToken = proto.start(FingerprintServiceDumpProto.USERS);
+
+            proto.write(FingerprintUserStatsProto.USER_ID, userId);
+            proto.write(FingerprintUserStatsProto.NUM_FINGERPRINTS,
+                    mFingerprintUtils.getFingerprintsForUser(mContext, userId).size());
+
+            // Normal fingerprint authentications (e.g. lockscreen)
+            final PerformanceStats normal = mPerformanceMap.get(userId);
+            if (normal != null) {
+                final long countsToken = proto.start(FingerprintUserStatsProto.NORMAL);
+                proto.write(FingerprintActionStatsProto.ACCEPT, normal.accept);
+                proto.write(FingerprintActionStatsProto.REJECT, normal.reject);
+                proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire);
+                proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout);
+                proto.end(countsToken);
+            }
+
+            // Statistics about secure fingerprint transactions (e.g. to unlock password
+            // storage, make secure purchases, etc.)
+            final PerformanceStats crypto = mPerformanceMap.get(userId);
+            if (crypto != null) {
+                final long countsToken = proto.start(FingerprintUserStatsProto.CRYPTO);
+                proto.write(FingerprintActionStatsProto.ACCEPT, crypto.accept);
+                proto.write(FingerprintActionStatsProto.REJECT, crypto.reject);
+                proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire);
+                proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout);
+                proto.end(countsToken);
+            }
+
+            proto.end(userToken);
+        }
+        proto.flush();
+    }
+
     @Override
     public void onStart() {
         publishBinderService(Context.FINGERPRINT_SERVICE, new FingerprintServiceWrapper());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c65514b..df1de11 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -5524,9 +5524,21 @@
             final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1);
             final ActivityInfo ai = getActivityInfo(comp, flags, userId);
             if (ai != null) {
-                final ResolveInfo ri = new ResolveInfo();
-                ri.activityInfo = ai;
-                list.add(ri);
+                // When specifying an explicit component, we prevent the activity from being
+                // used when either 1) the calling package is normal and the activity is within
+                // an ephemeral application or 2) the calling package is ephemeral and the
+                // activity is not visible to ephemeral applications.
+                boolean blockResolution =
+                        (ephemeralPkgName == null
+                                && (ai.applicationInfo.privateFlags
+                                        & ApplicationInfo.PRIVATE_FLAG_EPHEMERAL) != 0)
+                        || (ephemeralPkgName != null
+                                && (ai.flags & ActivityInfo.FLAG_VISIBLE_TO_EPHEMERAL) == 0);
+                if (!blockResolution) {
+                    final ResolveInfo ri = new ResolveInfo();
+                    ri.activityInfo = ai;
+                    list.add(ri);
+                }
             }
             return list;
         }
@@ -5604,10 +5616,10 @@
             } else {
                 final PackageParser.Package pkg = mPackages.get(pkgName);
                 if (pkg != null) {
-                    result = filterIfNotSystemUser(
+                    result = filterForEphemeral(filterIfNotSystemUser(
                             mActivities.queryIntentForPackage(
                                     intent, resolvedType, flags, pkg.activities, userId),
-                            userId);
+                            userId), ephemeralPkgName);
                 } else {
                     // the caller wants to resolve for a particular package; however, there
                     // were no installed results, so, try to find an ephemeral result
@@ -7627,6 +7639,11 @@
         }
     }
 
+    @Override
+    public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) {
+      // TODO(calin): b/32871170
+    }
+
     // TODO: this is not used nor needed. Delete it.
     @Override
     public boolean performDexOptIfNeeded(String packageName) {
diff --git a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
index 7928257..cdb69ce 100644
--- a/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
+++ b/services/core/java/com/android/server/pm/ShortcutRequestPinProcessor.java
@@ -48,7 +48,15 @@
      */
     private static class PinShortcutRequestInner extends IPinItemRequest.Stub {
         private final ShortcutRequestPinProcessor mProcessor;
-        public final ShortcutInfo shortcut;
+        /** Original shortcut passed by the app. */
+        public final ShortcutInfo shortcutOriginal;
+
+        /**
+         * Cloned shortcut that's passed to the launcher.  The notable difference from
+         * {@link #shortcutOriginal} is it must not have the intent.
+         */
+        public final ShortcutInfo shortcutForLauncher;
+
         private final IntentSender mResultIntent;
 
         public final String launcherPackage;
@@ -59,10 +67,12 @@
         private boolean mAccepted;
 
         private PinShortcutRequestInner(ShortcutRequestPinProcessor processor,
-                ShortcutInfo shortcut, IntentSender resultIntent,
+                ShortcutInfo shortcutOriginal, ShortcutInfo shortcutForLauncher,
+                IntentSender resultIntent,
                 String launcherPackage, int launcherUserId, boolean preExisting) {
             mProcessor = processor;
-            this.shortcut = shortcut;
+            this.shortcutOriginal = shortcutOriginal;
+            this.shortcutForLauncher = shortcutForLauncher;
             mResultIntent = resultIntent;
             this.launcherPackage = launcherPackage;
             this.launcherUserId = launcherUserId;
@@ -99,8 +109,8 @@
                 mAccepted = true;
             }
             if (DEBUG) {
-                Slog.d(TAG, "Launcher accepted shortcut. ID=" + shortcut.getId()
-                        + " package=" + shortcut.getPackage()
+                Slog.d(TAG, "Launcher accepted shortcut. ID=" + shortcutOriginal.getId()
+                        + " package=" + shortcutOriginal.getPackage()
                         + " options=" + options);
             }
 
@@ -163,7 +173,7 @@
         }
 
         // This is the shortcut that'll be sent to the launcher.
-        final ShortcutInfo shortcutToSend;
+        final ShortcutInfo shortcutForLauncher;
 
         if (existsAlready) {
             validateExistingShortcut(existing);
@@ -179,8 +189,10 @@
 
             // Pass a clone, not the original.
             // Note this will remove the intent and icons.
-            shortcutToSend = existing.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
-            shortcutToSend.clearFlags(ShortcutInfo.FLAG_PINNED);
+            shortcutForLauncher = existing.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
+
+            // FLAG_PINNED is still set, if it's pinned by other launchers.
+            shortcutForLauncher.clearFlags(ShortcutInfo.FLAG_PINNED);
         } else {
             // It doesn't exist, so it must have all mandatory fields.
             mService.validateShortcutForPinRequest(inShortcut);
@@ -191,17 +203,18 @@
             if (DEBUG) {
                 Slog.d(TAG, "resolved shortcut=" + inShortcut.toInsecureString());
             }
-            // TODO Remove the intent here -- don't pass shortcut intents to the launcher.
-            shortcutToSend = inShortcut;
+            // We should strip out the intent, but should preserve the icon.
+            shortcutForLauncher = inShortcut.clone(
+                    ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER_APPROVAL);
         }
 
         // Create a request object.
         final PinShortcutRequestInner inner =
-                new PinShortcutRequestInner(this, shortcutToSend, resultIntent,
+                new PinShortcutRequestInner(this, inShortcut, shortcutForLauncher, resultIntent,
                         launcherPackage, launcherUserId, existsAlready);
 
         final PinItemRequest outer = new PinItemRequest(PinItemRequest.REQUEST_TYPE_SHORTCUT,
-                shortcutToSend, inner);
+                shortcutForLauncher, inner);
 
         return startRequestConfirmActivity(launcherComponent, launcherUserId, outer);
     }
@@ -210,7 +223,7 @@
         // Make sure it's enabled.
         // (Because we can't always force enable it automatically as it may be a stale
         // manifest shortcut.)
-        Preconditions.checkState(shortcutInfo.isEnabled(),
+        Preconditions.checkArgument(shortcutInfo.isEnabled(),
                 "Shortcut ID=" + shortcutInfo + " already exists but disabled.");
 
     }
@@ -270,7 +283,7 @@
      */
     public boolean directPinShortcut(PinShortcutRequestInner request) {
 
-        final ShortcutInfo original = request.shortcut;
+        final ShortcutInfo original = request.shortcutOriginal;
         final int appUserId = original.getUserId();
         final String appPackageName = original.getPackage();
         final int launcherUserId = request.launcherUserId;
@@ -292,7 +305,7 @@
             try {
                 if (current == null) {
                     // It doesn't exist, so it must have all necessary fields.
-                    mService.validateShortcutForPinRequest(request.shortcut);
+                    mService.validateShortcutForPinRequest(original);
                 } else {
                     validateExistingShortcut(current);
                 }
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 424830b..436a53c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1865,9 +1865,10 @@
         synchronized (mLock) {
             throwIfUserLockedL(userId);
 
-            // TODO Make sure the caller is in the foreground.
+            Preconditions.checkState(isUidForegroundLocked(injectBinderCallingUid()),
+                    "Calling application must have a foreground activity or a foreground service");
 
-            // TODO Cancel all pending request from the same app.
+            // TODO Cancel all pending requests from the caller.
 
             // Send request to the launcher, if supported.
             ret = mShortcutRequestPinProcessor.requestPinShortcutLocked(shortcut, resultIntent);
diff --git a/services/core/jni/Android.mk b/services/core/jni/Android.mk
index 50a6095..3e8e420 100644
--- a/services/core/jni/Android.mk
+++ b/services/core/jni/Android.mk
@@ -74,6 +74,7 @@
     libutils \
     android.hardware.audio.common@2.0 \
     android.hardware.gnss@1.0 \
+    android.hardware.ir@1.0 \
     android.hardware.light@2.0 \
     android.hardware.power@1.0 \
     android.hardware.thermal@1.0 \
diff --git a/services/core/jni/com_android_server_ConsumerIrService.cpp b/services/core/jni/com_android_server_ConsumerIrService.cpp
index 7104870..5906e6b 100644
--- a/services/core/jni/com_android_server_ConsumerIrService.cpp
+++ b/services/core/jni/com_android_server_ConsumerIrService.cpp
@@ -23,87 +23,70 @@
 #include <stdlib.h>
 #include <utils/misc.h>
 #include <utils/Log.h>
-#include <hardware/hardware.h>
-#include <hardware/consumerir.h>
+#include <android/hardware/ir/1.0/IConsumerIr.h>
 #include <ScopedPrimitiveArray.h>
 
+using ::android::hardware::ir::V1_0::IConsumerIr;
+using ::android::hardware::ir::V1_0::ConsumerIrFreqRange;
+using ::android::hardware::hidl_vec;
+
 namespace android {
 
-static jlong halOpen(JNIEnv* /* env */, jobject /* obj */) {
-    hw_module_t const* module;
-    consumerir_device_t *dev;
-    int err;
+static sp<IConsumerIr> mHal;
 
-    err = hw_get_module(CONSUMERIR_HARDWARE_MODULE_ID, &module);
-    if (err != 0) {
-        ALOGE("Can't open consumer IR HW Module, error: %d", err);
-        return 0;
-    }
-
-    err = module->methods->open(module, CONSUMERIR_TRANSMITTER,
-            (hw_device_t **) &dev);
-    if (err < 0) {
-        ALOGE("Can't open consumer IR transmitter, error: %d", err);
-        return 0;
-    }
-
-    return reinterpret_cast<jlong>(dev);
+static jboolean halOpen(JNIEnv* /* env */, jobject /* obj */) {
+    // TODO(b/31632518)
+    mHal = IConsumerIr::getService("consumerir");
+    return mHal != nullptr;
 }
 
-static jint halTransmit(JNIEnv *env, jobject /* obj */, jlong halObject,
-   jint carrierFrequency, jintArray pattern) {
-    int ret;
-
-    consumerir_device_t *dev = reinterpret_cast<consumerir_device_t*>(halObject);
+static jint halTransmit(JNIEnv *env, jobject /* obj */, jint carrierFrequency,
+   jintArray pattern) {
     ScopedIntArrayRO cPattern(env, pattern);
     if (cPattern.get() == NULL) {
         return -EINVAL;
     }
-    jsize patternLength = cPattern.size();
+    hidl_vec<int32_t> patternVec;
+    patternVec.setToExternal(const_cast<int32_t*>(cPattern.get()), cPattern.size());
 
-    ret = dev->transmit(dev, carrierFrequency, cPattern.get(), patternLength);
-
-    return reinterpret_cast<jint>(ret);
+    bool success = mHal->transmit(carrierFrequency, patternVec, cPattern.size());
+    return success ? 0 : -1;
 }
 
-static jintArray halGetCarrierFrequencies(JNIEnv *env, jobject /* obj */,
-    jlong halObject) {
-    consumerir_device_t *dev = reinterpret_cast<consumerir_device_t*>(halObject);
-    consumerir_freq_range_t *ranges;
+static jintArray halGetCarrierFrequencies(JNIEnv *env, jobject /* obj */) {
     int len;
+    hidl_vec<ConsumerIrFreqRange> ranges;
+    bool success;
 
-    len = dev->get_num_carrier_freqs(dev);
-    if (len <= 0)
-        return NULL;
+    auto cb = [&](bool s, hidl_vec<ConsumerIrFreqRange> vec) {
+            ranges = vec;
+            success = s;
+    };
+    mHal->getCarrierFreqs(cb);
 
-    ranges = new consumerir_freq_range_t[len];
-
-    len = dev->get_carrier_freqs(dev, len, ranges);
-    if (len <= 0) {
-        delete[] ranges;
+    if (!success) {
         return NULL;
     }
+    len = ranges.size();
 
     int i;
     ScopedIntArrayRW freqsOut(env, env->NewIntArray(len*2));
     jint *arr = freqsOut.get();
     if (arr == NULL) {
-        delete[] ranges;
         return NULL;
     }
     for (i = 0; i < len; i++) {
-        arr[i*2] = ranges[i].min;
-        arr[i*2+1] = ranges[i].max;
+        arr[i*2] = static_cast<jint>(ranges[i].min);
+        arr[i*2+1] = static_cast<jint>(ranges[i].max);
     }
 
-    delete[] ranges;
     return freqsOut.getJavaArray();
 }
 
 static const JNINativeMethod method_table[] = {
-    { "halOpen", "()J", (void *)halOpen },
-    { "halTransmit", "(JI[I)I", (void *)halTransmit },
-    { "halGetCarrierFrequencies", "(J)[I", (void *)halGetCarrierFrequencies},
+    { "halOpen", "()Z", (void *)halOpen },
+    { "halTransmit", "(I[I)I", (void *)halTransmit },
+    { "halGetCarrierFrequencies", "()[I", (void *)halGetCarrierFrequencies},
 };
 
 int register_android_server_ConsumerIrService(JNIEnv *env) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
index 5f464bd..49ae2bc3 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
@@ -46,11 +46,11 @@
 
     private final DevicePolicyManagerService mDpm;
     private final PackageManagerInternal mPm;
+    private final AtomicBoolean mIsLoggingEnabled = new AtomicBoolean(false);
 
     private IIpConnectivityMetrics mIpConnectivityMetrics;
     private ServiceThread mHandlerThread;
     private NetworkLoggingHandler mNetworkLoggingHandler;
-    private AtomicBoolean mIsLoggingEnabled;
 
     private final INetdEventCallback mNetdEventCallback = new INetdEventCallback.Stub() {
         @Override
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 6ec25c5..be13499 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -32,6 +32,7 @@
 import android.os.Environment;
 import android.os.FactoryTest;
 import android.os.FileUtils;
+import android.os.IIncidentManager;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
@@ -1635,6 +1636,19 @@
                 }
                 traceEnd();
 
+                traceBeginAndSlog("IncidentDaemonReady");
+                try {
+                    // TODO: Switch from checkService to getService once it's always
+                    // in the build and should reliably be there.
+                    final IIncidentManager incident = IIncidentManager.Stub.asInterface(
+                            ServiceManager.checkService("incident"));
+                    if (incident != null) incident.systemRunning();
+                } catch (Throwable e) {
+                    reportWtf("Notifying incident daemon running", e);
+                }
+                traceEnd();
+                
+
                 traceEnd();  // PhaseActivityManagerReady
             }
         });
diff --git a/services/print/java/com/android/server/print/RemotePrintService.java b/services/print/java/com/android/server/print/RemotePrintService.java
index fa37576..fb78457 100644
--- a/services/print/java/com/android/server/print/RemotePrintService.java
+++ b/services/print/java/com/android/server/print/RemotePrintService.java
@@ -132,8 +132,6 @@
     }
 
     private void handleDestroy() {
-        throwIfDestroyed();
-
         // Stop tracking printers.
         stopTrackingAllPrinters();
 
@@ -174,7 +172,6 @@
     }
 
     private void handleOnAllPrintJobsHandled() {
-        throwIfDestroyed();
         mHasActivePrintJobs = false;
         if (!isBound()) {
             // The service is dead and neither has active jobs nor discovery
@@ -208,7 +205,6 @@
     }
 
     private void handleRequestCancelPrintJob(final PrintJobInfo printJob) {
-        throwIfDestroyed();
         if (!isBound()) {
             ensureBound();
             mPendingCommands.add(new Runnable() {
@@ -235,7 +231,6 @@
     }
 
     private void handleOnPrintJobQueued(final PrintJobInfo printJob) {
-        throwIfDestroyed();
         mHasActivePrintJobs = true;
         if (!isBound()) {
             ensureBound();
@@ -262,7 +257,6 @@
     }
 
     private void handleCreatePrinterDiscoverySession() {
-        throwIfDestroyed();
         mHasPrinterDiscoverySession = true;
         if (!isBound()) {
             ensureBound();
@@ -289,7 +283,6 @@
     }
 
     private void handleDestroyPrinterDiscoverySession() {
-        throwIfDestroyed();
         mHasPrinterDiscoverySession = false;
         if (!isBound()) {
             // The service is dead and neither has active jobs nor discovery
@@ -328,7 +321,6 @@
     }
 
     private void handleStartPrinterDiscovery(final List<PrinterId> priorityList) {
-        throwIfDestroyed();
         // Take a note that we are doing discovery.
         mDiscoveryPriorityList = new ArrayList<PrinterId>();
         if (priorityList != null) {
@@ -359,7 +351,6 @@
     }
 
     private void handleStopPrinterDiscovery() {
-        throwIfDestroyed();
         // We are not doing discovery anymore.
         mDiscoveryPriorityList = null;
         if (!isBound()) {
@@ -392,7 +383,6 @@
     }
 
     private void handleValidatePrinters(final List<PrinterId> printerIds) {
-        throwIfDestroyed();
         if (!isBound()) {
             ensureBound();
             mPendingCommands.add(new Runnable() {
@@ -419,23 +409,40 @@
     }
 
     /**
-     * Request the custom printer icon for a printer.
+     * Queue a request for a custom printer icon for a printer.
      *
      * @param printerId the id of the printer the icon should be loaded for
-     * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon()
+     * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon
      */
     public void requestCustomPrinterIcon(@NonNull PrinterId printerId) {
-        try {
-            if (isBound()) {
-                mPrintService.requestCustomPrinterIcon(printerId);
+        mHandler.obtainMessage(MyHandler.MSG_REQUEST_CUSTOM_PRINTER_ICON,
+                printerId).sendToTarget();
+    }
+
+    /**
+     * Request a custom printer icon for a printer.
+     *
+     * @param printerId the id of the printer the icon should be loaded for
+     * @see android.print.PrinterInfo.Builder#setHasCustomPrinterIcon
+     */
+    private void handleRequestCustomPrinterIcon(@NonNull PrinterId printerId) {
+        if (!isBound()) {
+            ensureBound();
+            mPendingCommands.add(() -> handleRequestCustomPrinterIcon(printerId));
+        } else {
+            if (DEBUG) {
+                Slog.i(LOG_TAG, "[user: " + mUserId + "] requestCustomPrinterIcon()");
             }
-        } catch (RemoteException re) {
-            Slog.e(LOG_TAG, "Error requesting icon for " + printerId, re);
+
+            try {
+                mPrintService.requestCustomPrinterIcon(printerId);
+            } catch (RemoteException re) {
+                Slog.e(LOG_TAG, "Error requesting icon for " + printerId, re);
+            }
         }
     }
 
     private void handleStartPrinterStateTracking(final @NonNull PrinterId printerId) {
-        throwIfDestroyed();
         // Take a note we are tracking the printer.
         if (mTrackedPrinterList == null) {
             mTrackedPrinterList = new ArrayList<PrinterId>();
@@ -467,7 +474,6 @@
     }
 
     private void handleStopPrinterStateTracking(final PrinterId printerId) {
-        throwIfDestroyed();
         // We are no longer tracking the printer.
         if (mTrackedPrinterList == null || !mTrackedPrinterList.remove(printerId)) {
             return;
@@ -581,12 +587,6 @@
         }
     }
 
-    private void throwIfDestroyed() {
-        if (mDestroyed) {
-            throw new IllegalStateException("Cannot interact with a destroyed service");
-        }
-    }
-
     private class RemoteServiceConneciton implements ServiceConnection {
         @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
@@ -657,6 +657,7 @@
         public static final int MSG_ON_PRINT_JOB_QUEUED = 10;
         public static final int MSG_DESTROY = 11;
         public static final int MSG_BINDER_DIED = 12;
+        public static final int MSG_REQUEST_CUSTOM_PRINTER_ICON = 13;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
@@ -665,6 +666,11 @@
         @Override
         @SuppressWarnings("unchecked")
         public void handleMessage(Message message) {
+            if (mDestroyed) {
+                Slog.w(LOG_TAG, "Not handling " + message + " as service for " + mComponentName
+                        + " is already destroyed");
+                return;
+            }
             switch (message.what) {
                 case MSG_CREATE_PRINTER_DISCOVERY_SESSION: {
                     handleCreatePrinterDiscoverySession();
@@ -719,6 +725,11 @@
                 case MSG_BINDER_DIED: {
                     handleBinderDied();
                 } break;
+
+                case MSG_REQUEST_CUSTOM_PRINTER_ICON: {
+                    PrinterId printerId = (PrinterId) message.obj;
+                    handleRequestCustomPrinterIcon(printerId);
+                } break;
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 50911cb..c653b8e 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -16,10 +16,13 @@
 
 package com.android.server;
 
+import static android.net.NetworkRecommendationProvider.EXTRA_RECOMMENDATION_RESULT;
+import static android.net.NetworkRecommendationProvider.EXTRA_SEQUENCE;
 import static android.net.NetworkScoreManager.CACHE_FILTER_NONE;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
@@ -28,12 +31,15 @@
 import static org.mockito.Matchers.anyListOf;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.isA;
 import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.Manifest.permission;
@@ -44,37 +50,42 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.net.INetworkRecommendationProvider;
 import android.net.INetworkScoreCache;
 import android.net.NetworkKey;
-import android.net.NetworkScoreManager;
 import android.net.NetworkScorerAppManager;
 import android.net.NetworkScorerAppManager.NetworkScorerAppData;
+import android.net.RecommendationRequest;
+import android.net.RecommendationResult;
 import android.net.ScoredNetwork;
 import android.net.WifiKey;
+import android.net.wifi.WifiConfiguration;
+import android.os.Bundle;
 import android.os.IBinder;
+import android.os.IRemoteCallback;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.Settings;
-import android.provider.Settings.Global;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.MediumTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import com.android.internal.R;
 import com.android.server.devicepolicy.MockUtils;
 
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
-import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
 
 /**
  * Tests for {@link NetworkScoreService}.
@@ -85,12 +96,8 @@
     private static final ScoredNetwork SCORED_NETWORK =
             new ScoredNetwork(new NetworkKey(new WifiKey("\"ssid\"", "00:00:00:00:00:00")),
                     null /* rssiCurve*/);
-    private static final NetworkScorerAppData PREV_SCORER = new NetworkScorerAppData(
-            "prevPackageName", 0, "prevScorerName", null /* configurationActivityClassName */,
-            "prevScoringServiceClass");
-    private static final NetworkScorerAppData NEW_SCORER = new NetworkScorerAppData(
-            "newPackageName", 1, "newScorerName", null /* configurationActivityClassName */,
-            "newScoringServiceClass");
+    private static final NetworkScorerAppData NEW_SCORER =
+        new NetworkScorerAppData("newPackageName", 1, "newScoringServiceClass");
 
     @Mock private PackageManager mPackageManager;
     @Mock private NetworkScorerAppManager mNetworkScorerAppManager;
@@ -98,10 +105,12 @@
     @Mock private Resources mResources;
     @Mock private INetworkScoreCache.Stub mNetworkScoreCache, mNetworkScoreCache2;
     @Mock private IBinder mIBinder, mIBinder2;
+    @Mock private INetworkRecommendationProvider mRecommendationProvider;
     @Captor private ArgumentCaptor<List<ScoredNetwork>> mScoredNetworkCaptor;
 
     private ContentResolver mContentResolver;
     private NetworkScoreService mNetworkScoreService;
+    private RecommendationRequest mRecommendationRequest;
 
     @Before
     public void setUp() throws Exception {
@@ -112,44 +121,9 @@
         when(mContext.getContentResolver()).thenReturn(mContentResolver);
         when(mContext.getResources()).thenReturn(mResources);
         mNetworkScoreService = new NetworkScoreService(mContext, mNetworkScorerAppManager);
-    }
-
-    @Test
-    public void testSystemReady_networkScorerProvisioned() throws Exception {
-        Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 1);
-
-        mNetworkScoreService.systemReady();
-
-        verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString());
-    }
-
-    @Test
-    public void testSystemReady_networkScorerNotProvisioned_defaultScorer() throws Exception {
-        Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0);
-
-        when(mResources.getString(R.string.config_defaultNetworkScorerPackageName))
-                .thenReturn(NEW_SCORER.mPackageName);
-
-        mNetworkScoreService.systemReady();
-
-        verify(mNetworkScorerAppManager).setActiveScorer(NEW_SCORER.mPackageName);
-        assertEquals(1,
-                Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED));
-
-    }
-
-    @Test
-    public void testSystemReady_networkScorerNotProvisioned_noDefaultScorer() throws Exception {
-        Settings.Global.putInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED, 0);
-
-        when(mResources.getString(R.string.config_defaultNetworkScorerPackageName))
-                .thenReturn(null);
-
-        mNetworkScoreService.systemReady();
-
-        verify(mNetworkScorerAppManager, never()).setActiveScorer(anyString());
-        assertEquals(1,
-                Settings.Global.getInt(mContentResolver, Global.NETWORK_SCORING_PROVISIONED));
+        WifiConfiguration configuration = new WifiConfiguration();
+        mRecommendationRequest = new RecommendationRequest.Builder()
+            .setCurrentRecommendedWifiConfig(configuration).build();
     }
 
     @Test
@@ -159,13 +133,126 @@
         mNetworkScoreService.systemRunning();
 
         verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
-                new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))),
+                new ComponentName(NEW_SCORER.packageName,
+                    NEW_SCORER.recommendationServiceClassName))),
                 any(ServiceConnection.class),
                 eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
                 eq(UserHandle.SYSTEM));
     }
 
     @Test
+    public void testRequestScores_noPermission() throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+            .enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED),
+                anyString());
+        try {
+            mNetworkScoreService.requestScores(null);
+            fail("BROADCAST_NETWORK_PRIVILEGED not enforced.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestScores_providerNotConnected() throws Exception {
+        assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0]));
+        verifyZeroInteractions(mRecommendationProvider);
+    }
+
+    @Test
+    public void testRequestScores_providerThrowsRemoteException() throws Exception {
+        injectProvider();
+        doThrow(new RemoteException()).when(mRecommendationProvider)
+            .requestScores(any(NetworkKey[].class));
+
+        assertFalse(mNetworkScoreService.requestScores(new NetworkKey[0]));
+    }
+
+    @Test
+    public void testRequestScores_providerAvailable() throws Exception {
+        injectProvider();
+
+        final NetworkKey[] networks = new NetworkKey[0];
+        assertTrue(mNetworkScoreService.requestScores(networks));
+        verify(mRecommendationProvider).requestScores(networks);
+    }
+
+    @Test
+    public void testRequestRecommendation_noPermission() throws Exception {
+        doThrow(new SecurityException()).when(mContext)
+            .enforceCallingOrSelfPermission(eq(permission.BROADCAST_NETWORK_PRIVILEGED),
+                anyString());
+        try {
+            mNetworkScoreService.requestRecommendation(mRecommendationRequest);
+            fail("BROADCAST_NETWORK_PRIVILEGED not enforced.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestRecommendation_mainThread() throws Exception {
+        when(mContext.getMainLooper()).thenReturn(Looper.myLooper());
+        try {
+            mNetworkScoreService.requestRecommendation(mRecommendationRequest);
+            fail("requestRecommendation run on main thread.");
+        } catch (RuntimeException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestRecommendation_providerNotConnected() throws Exception {
+        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+
+        final RecommendationResult result =
+                mNetworkScoreService.requestRecommendation(mRecommendationRequest);
+        assertNotNull(result);
+        assertEquals(mRecommendationRequest.getCurrentSelectedConfig(),
+                result.getWifiConfiguration());
+    }
+
+    @Test
+    public void testRequestRecommendation_providerThrowsRemoteException() throws Exception {
+        injectProvider();
+        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+        doThrow(new RemoteException()).when(mRecommendationProvider)
+                .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
+                        anyInt());
+
+        final RecommendationResult result =
+                mNetworkScoreService.requestRecommendation(mRecommendationRequest);
+        assertNotNull(result);
+        assertEquals(mRecommendationRequest.getCurrentSelectedConfig(),
+                result.getWifiConfiguration());
+    }
+
+    @Test
+    public void testRequestRecommendation_resultReturned() throws Exception {
+        injectProvider();
+        when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+        final WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.SSID = "testRequestRecommendation_resultReturned";
+        final RecommendationResult providerResult =
+                new RecommendationResult(wifiConfiguration);
+        final Bundle bundle = new Bundle();
+        bundle.putParcelable(EXTRA_RECOMMENDATION_RESULT, providerResult);
+        doAnswer(invocation -> {
+            bundle.putInt(EXTRA_SEQUENCE, invocation.getArgumentAt(2, int.class));
+            invocation.getArgumentAt(1, IRemoteCallback.class).sendResult(bundle);
+            return null;
+        }).when(mRecommendationProvider)
+                .requestRecommendation(eq(mRecommendationRequest), isA(IRemoteCallback.class),
+                        anyInt());
+
+        final RecommendationResult result =
+                mNetworkScoreService.requestRecommendation(mRecommendationRequest);
+        assertNotNull(result);
+        assertEquals(providerResult.getWifiConfiguration().SSID,
+                result.getWifiConfiguration().SSID);
+    }
+
+    @Test
     public void testUpdateScores_notActiveScorer() {
         when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
 
@@ -288,45 +375,6 @@
     }
 
     @Test
-    public void testSetActiveScorer_failure() throws RemoteException {
-        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER);
-        when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(false);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
-                CACHE_FILTER_NONE);
-
-        boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
-
-        assertFalse(success);
-        verify(mNetworkScoreCache).clearScores();
-        verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
-                new ComponentName(PREV_SCORER.mPackageName, PREV_SCORER.mScoringServiceClassName))),
-                any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
-                eq(UserHandle.SYSTEM));
-    }
-
-    @Test
-    public void testSetActiveScorer_success() throws RemoteException {
-        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, NEW_SCORER);
-        when(mNetworkScorerAppManager.setActiveScorer(NEW_SCORER.mPackageName)).thenReturn(true);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
-                CACHE_FILTER_NONE);
-
-        boolean success = mNetworkScoreService.setActiveScorer(NEW_SCORER.mPackageName);
-
-        assertTrue(success);
-        verify(mNetworkScoreCache).clearScores();
-        verify(mContext).bindServiceAsUser(MockUtils.checkIntent(new Intent().setComponent(
-                new ComponentName(NEW_SCORER.mPackageName, NEW_SCORER.mScoringServiceClassName))),
-                any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
-                eq(UserHandle.SYSTEM));
-        verify(mContext, times(2)).sendBroadcastAsUser(
-                MockUtils.checkIntentAction(NetworkScoreManager.ACTION_SCORER_CHANGED),
-                eq(UserHandle.SYSTEM));
-    }
-
-    @Test
     public void testDisableScoring_notActiveScorer_noBroadcastNetworkPermission() {
         when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
         when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
@@ -338,48 +386,6 @@
         } catch (SecurityException e) {
             // expected
         }
-
-    }
-
-    @Test
-    public void testDisableScoring_activeScorer() throws RemoteException {
-        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(true);
-        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
-        when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
-                CACHE_FILTER_NONE);
-
-        mNetworkScoreService.disableScoring();
-
-        verify(mNetworkScoreCache).clearScores();
-        verify(mContext).sendBroadcastAsUser(
-                MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED)
-                        .setPackage(PREV_SCORER.mPackageName)),
-                eq(UserHandle.SYSTEM));
-        verify(mContext, never()).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
-    }
-
-    @Test
-    public void testDisableScoring_notActiveScorer_hasBroadcastNetworkPermission()
-            throws RemoteException {
-        when(mNetworkScorerAppManager.isCallerActiveScorer(anyInt())).thenReturn(false);
-        when(mContext.checkCallingOrSelfPermission(permission.BROADCAST_NETWORK_PRIVILEGED))
-                .thenReturn(PackageManager.PERMISSION_GRANTED);
-        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(PREV_SCORER, null);
-        when(mNetworkScorerAppManager.setActiveScorer(null)).thenReturn(true);
-        mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
-                CACHE_FILTER_NONE);
-
-        mNetworkScoreService.disableScoring();
-
-        verify(mNetworkScoreCache).clearScores();
-        verify(mContext).sendBroadcastAsUser(
-                MockUtils.checkIntent(new Intent(NetworkScoreManager.ACTION_SCORER_CHANGED)
-                        .setPackage(PREV_SCORER.mPackageName)),
-                eq(UserHandle.SYSTEM));
-        verify(mContext, never()).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
     }
 
     @Test
@@ -434,4 +440,24 @@
 
         assertFalse(stringWriter.toString().isEmpty());
     }
+
+    // "injects" the mock INetworkRecommendationProvider into the NetworkScoreService.
+    private void injectProvider() {
+        final ComponentName componentName = new ComponentName(NEW_SCORER.packageName,
+                NEW_SCORER.recommendationServiceClassName);
+        when(mNetworkScorerAppManager.getActiveScorer()).thenReturn(NEW_SCORER);
+        when(mContext.bindServiceAsUser(isA(Intent.class), isA(ServiceConnection.class), anyInt(),
+                isA(UserHandle.class))).thenAnswer(new Answer<Boolean>() {
+            @Override
+            public Boolean answer(InvocationOnMock invocation) throws Throwable {
+                IBinder mockBinder = mock(IBinder.class);
+                when(mockBinder.queryLocalInterface(anyString()))
+                        .thenReturn(mRecommendationProvider);
+                invocation.getArgumentAt(1, ServiceConnection.class)
+                        .onServiceConnected(componentName, mockBinder);
+                return true;
+            }
+        });
+        mNetworkScoreService.systemRunning();
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index cb27af1..6bc4c19 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -71,6 +71,7 @@
 import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.test.InstrumentationTestCase;
@@ -1289,6 +1290,13 @@
     }
 
     /**
+     * Make a shortcut with an ID only.
+     */
+    protected ShortcutInfo makeShortcutIdOnly(String id) {
+        return new ShortcutInfo.Builder(mClientContext, id).build();
+    }
+
+    /**
      * Make a shortcut with an ID.
      */
     protected ShortcutInfo makeShortcut(String id) {
@@ -1297,12 +1305,19 @@
                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
     }
 
+    @Deprecated // Title was renamed to short label.
     protected ShortcutInfo makeShortcutWithTitle(String id, String title) {
         return makeShortcut(
                 id, title, /* activity =*/ null, /* icon =*/ null,
                 makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
     }
 
+    protected ShortcutInfo makeShortcutWithShortLabel(String id, String shortLabel) {
+        return makeShortcut(
+                id, shortLabel, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0);
+    }
+
     /**
      * Make a shortcut with an ID and timestamp.
      */
@@ -1695,6 +1710,13 @@
         return getLauncherShortcuts(launcher, userId, ShortcutQuery.FLAG_GET_PINNED);
     }
 
+    protected List<ShortcutInfo> getShortcutAsLauncher(int targetUserId) {
+        final ShortcutQuery q = new ShortcutQuery();
+        q.setQueryFlags(ShortcutQuery.FLAG_MATCH_DYNAMIC | ShortcutQuery.FLAG_MATCH_DYNAMIC
+                | ShortcutQuery.FLAG_MATCH_PINNED);
+        return mLauncherApps.getShortcuts(q, UserHandle.of(targetUserId));
+    }
+
     protected ShortcutInfo getShortcutInfoAsLauncher(String packageName, String shortcutId,
             int userId) {
         final List<ShortcutInfo> infoList =
@@ -1968,7 +1990,8 @@
     public static List<ShortcutInfo> assertAllHaveIcon(
             List<ShortcutInfo> actualShortcuts) {
         for (ShortcutInfo s : actualShortcuts) {
-            assertTrue("ID " + s.getId() + " has no icon ", s.hasIconFile() || s.hasIconResource());
+            assertTrue("ID " + s.getId() + " has no icon ",
+                    s.hasIconFile() || s.hasIconResource() || s.getIcon() != null);
         }
         return actualShortcuts;
     }
@@ -2030,4 +2053,31 @@
         return ri(PACKAGE_FALLBACK_LAUNCHER, PACKAGE_FALLBACK_LAUNCHER_NAME, true,
                 PACKAGE_FALLBACK_LAUNCHER_PRIORITY);
     }
+
+    protected void makeCallerForeground() {
+        try {
+            mService.mUidObserver.onUidStateChanged(
+                    mInjectedCallingUid, ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    protected void makeCallerBackground() {
+        try {
+            mService.mUidObserver.onUidStateChanged(
+                    mInjectedCallingUid, ActivityManager.PROCESS_STATE_TOP_SLEEPING);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    protected void publishManifestShortcutsAsCaller(int resId) {
+        addManifestShortcutResource(
+                new ComponentName(getCallingPackage(), ShortcutActivity.class.getName()),
+                resId);
+        updatePackageVersion(getCallingPackage(), 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(getCallingPackage(), getCallingUserId()));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
index de344c2..fbf0ed2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest8.java
@@ -15,7 +15,10 @@
  */
 package com.android.server.pm;
 
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertForLauncherCallbackNoThrow;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
 
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
@@ -31,10 +34,14 @@
 import android.content.pm.LauncherApps.PinItemRequest;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
 import android.os.UserHandle;
+import android.test.MoreAsserts;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Pair;
 
+import com.android.frameworks.servicestests.R;
+
 import org.mockito.ArgumentCaptor;
 
 /**
@@ -45,6 +52,11 @@
  -r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
  adb shell am instrument -e class com.android.server.pm.ShortcutManagerTest8 \
  -w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
+
+ * TODO for CTS
+ * - Foreground check.
+ * - Reading icons from requested shortcuts.
+ * - Invalid pre-approved token.
  */
 @SmallTest
 public class ShortcutManagerTest8 extends BaseShortcutManagerTest {
@@ -56,6 +68,14 @@
         mProcessor = mService.getShortcutRequestPinProcessorForTest();
     }
 
+    @Override
+    protected void setCaller(String packageName, int userId) {
+        super.setCaller(packageName, userId);
+
+        // Note during this test, assume all callers are in the foreground by default.
+        makeCallerForeground();
+    }
+
     public void testGetParentOrSelfUserId() {
         assertEquals(USER_0, mService.getParentOrSelfUserId(USER_0));
         assertEquals(USER_10, mService.getParentOrSelfUserId(USER_10));
@@ -188,6 +208,23 @@
                 actualIntent.getFlags());
     }
 
+    public void testNotForeground() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            makeCallerBackground();
+
+            assertExpectException(IllegalStateException.class, "foreground activity", () -> {
+                assertTrue(mManager.requestPinShortcut(makeShortcut("s1"),
+                        /* resultIntent= */ null));
+            });
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+            verify(mServiceContext, times(0)).startActivityAsUser(
+                    any(Intent.class), any(UserHandle.class));
+        });
+    }
+
     private void assertPinItemRequest(PinItemRequest actualRequest) {
         assertNotNull(actualRequest);
 
@@ -203,14 +240,15 @@
         setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
         setDefaultLauncher(USER_10, mMainActivityFetcher.apply(LAUNCHER_2, USER_10));
 
+        final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
+
         runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
-            ShortcutInfo s1 = makeShortcut("s1");
+            ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32);
 
             assertTrue(mManager.requestPinShortcut(s1,
                     resultIntent == null ? null : resultIntent.getIntentSender()));
 
-            verify(mServiceContext, times(0))
-                    .sendIntentSender(any(IntentSender.class));
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
 
             // Shortcut shouldn't be registered yet.
             assertWith(getCallerShortcuts())
@@ -232,16 +270,18 @@
 
             assertWith(request.getShortcutInfo())
                     .haveIds("s1")
-                    .areAllOrphan();
+                    .areAllOrphan()
+                    .areAllWithNoIntent();
 
-            // Can't test icons; need to test on CTS.
+            assertAllHaveIcon(list(request.getShortcutInfo()));
 
             // Accept the request.
-            request.accept();
+            assertForLauncherCallbackNoThrow(mLauncherApps,
+                    () -> assertTrue(request.accept()))
+                    .assertCallbackCalledForPackageAndUser(CALLING_PACKAGE_1, HANDLE_USER_P0)
+                    .haveIds("s1");
         });
 
-        // Check from the launcher side, including callback
-
         // This method is always called, even with PI == null.
         if (resultIntent == null) {
             verify(mServiceContext, times(1)).sendIntentSender(eq(null));
@@ -254,7 +294,8 @@
                     .haveIds("s1")
                     .areAllNotDynamic()
                     .areAllEnabled()
-                    .areAllPinned();
+                    .areAllPinned()
+                    .areAllWithIntent();
         });
     }
 
@@ -269,23 +310,919 @@
         checkRequestPinShortcut(resultIntent);
     }
 
+    public void testRequestPinShortcut_dynamicExists() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            // Create dynamic shortcut
+            ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32);
+            assertTrue(mManager.setDynamicShortcuts(list(s1)));
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllNotPinned();
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllNotPinned()
+                    .areAllWithNoIntent();
+
+            assertAllHaveIcon(list(request.getShortcutInfo()));
+
+            // Accept the request.
+            assertTrue(request.accept());
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllEnabled()
+                    .areAllPinned();
+        });
+    }
+
+    public void testRequestPinShortcut_manifestExists() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllNotPinned();
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllNotPinned()
+                    .areAllWithNoIntent();
+
+            assertAllHaveIcon(list(request.getShortcutInfo()));
+
+            // Accept the request.
+            assertTrue(request.accept());
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllEnabled()
+                    .areAllPinned();
+        });
+    }
+
+    public void testRequestPinShortcut_dynamicExists_alreadyPinned() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllPinned();
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
+                    /* resultIntent=*/ null));
+
+            // The intent should be sent right away.
+            verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class));
+        });
+    }
+
+    public void testRequestPinShortcut_manifestExists_alreadyPinned() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllPinned();
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
+                    /* resultIntent=*/ null));
+
+            // The intent should be sent right away.
+            verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class));
+        });
+    }
+
+    public void testRequestPinShortcut_wasDynamic_alreadyPinned() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            mManager.removeAllDynamicShortcuts();
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllNotDynamic()
+                    .areAllEnabled()
+                    .areAllPinned();
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
+                    /* resultIntent=*/ null));
+
+            // The intent should be sent right away.
+            verify(mServiceContext, times(1)).sendIntentSender(any(IntentSender.class));
+        });
+    }
+
+    public void testRequestPinShortcut_wasDynamic_disabled_alreadyPinned() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            mManager.disableShortcuts(list("s1"));
+
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllNotDynamic()
+                    .areAllDisabled()
+                    .areAllPinned();
+
+            assertExpectException(IllegalArgumentException.class, "exists but disabled", () -> {
+                mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
+                        /* resultIntent=*/ null);
+            });
+
+            // Shouldn't be called.
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+        });
+    }
+
+    public void testRequestPinShortcut_wasManifest_alreadyPinned() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            publishManifestShortcutsAsCaller(R.xml.shortcut_0);
+
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1")
+                    .areAllNotManifest()
+                    .areAllDisabled()
+                    .areAllPinned();
+
+            assertExpectException(IllegalArgumentException.class, "exists but disabled", () -> {
+                mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
+                        /* resultIntent=*/ null);
+            });
+
+            // Shouldn't be called.
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+        });
+    }
+
+    public void testRequestPinShortcut_dynamicExists_alreadyPinnedByAnother() {
+        // Initially all launchers have the shortcut permission, until we call setDefaultLauncher().
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"))));
+        });
+
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
+        });
+
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllPinned();
+
+            // The shortcut is already pinned, but not by the current launcher, so it'll still
+            // invoke the whole flow.
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllNotPinned() // Note it's not pinned by this launcher.
+                    .areAllWithNoIntent();
+
+            // Accept the request.
+            assertTrue(request.accept());
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllEnabled()
+                    .areAllPinned();
+        });
+    }
+
+    public void testRequestPinShortcut_manifestExists_alreadyPinnedByAnother() {
+        // Initially all launchers have the shortcut permission, until we call setDefaultLauncher().
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
+        });
+
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
+        });
+
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllPinned();
+
+            // The shortcut is already pinned, but not by the current launcher, so it'll still
+            // invoke the whole flow.
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllNotPinned() // Note it's not pinned by this launcher.
+                    .areAllWithNoIntent();
+
+            // Accept the request.
+            assertTrue(request.accept());
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllEnabled()
+                    .areAllPinned();
+        });
+    }
+
+    /**
+     * The launcher already has a pinned shortuct.  The new one should be added, not replace
+     * the existing one.
+     */
+    public void testRequestPinShortcut_launcherAlreadyHasPinned() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.setDynamicShortcuts(list(makeShortcut("s1"), makeShortcut("s2"))));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_P0);
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllNotPinned()
+                    .areAllWithNoIntent();
+
+            // Accept the request.
+            assertTrue(request.accept());
+
+            assertWith(getShortcutAsLauncher(USER_P0))
+                    .haveIds("s1", "s2")
+                    .areAllDynamic()
+                    .areAllEnabled()
+                    .areAllPinned();
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1", "s2")
+                    .areAllDynamic()
+                    .areAllEnabled()
+                    .areAllPinned();
+        });
+    }
+
+    /**
+     * When trying to pin an existing shortcut, the new fields shouldn't override existing fields.
+     */
+    public void testRequestPinShortcut_dynamicExists_titleWontChange() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            // Create dynamic shortcut
+            ShortcutInfo s1 = makeShortcutWithIcon("s1", res32x32);
+            assertTrue(mManager.setDynamicShortcuts(list(s1)));
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("s1", "xxx"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllNotPinned();
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllNotPinned()
+                    .areAllWithNoIntent();
+
+            assertAllHaveIcon(list(request.getShortcutInfo()));
+
+            // Accept the request.
+            assertTrue(request.accept());
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllEnabled()
+                    .areAllPinned()
+                    .forShortcutWithId("s1", (si) -> {
+                        // Still the original title.
+                        assertEquals("Title-s1", si.getShortLabel());
+                    });
+        });
+    }
+
+    /**
+     * When trying to pin an existing shortcut, the new fields shouldn't override existing fields.
+     */
+    public void testRequestPinShortcut_manifestExists_titleWontChange() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("ms1", "xxx"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllNotPinned();
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllNotPinned()
+                    .areAllWithNoIntent();
+
+            assertAllHaveIcon(list(request.getShortcutInfo()));
+
+            // Accept the request.
+            assertTrue(request.accept());
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllEnabled()
+                    .areAllPinned()
+                    .forShortcutWithId("ms1", (si) -> {
+                        // Still the original title.
+                        // Title should be something like:
+                        // "string-com.android.test.1-user:20-res:2131034112/en"
+                        MoreAsserts.assertContainsRegex("^string-", si.getShortLabel().toString());
+                    });
+        });
+    }
+
+    /**
+     * The dynamic shortcut existed, but before accepting(), it's removed.  Because the request
+     * has a partial shortcut, accept() should fail.
+     */
+    public void testRequestPinShortcut_dynamicExists_thenRemoved_error() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            // Create dynamic shortcut
+            ShortcutInfo s1 = makeShortcut("s1");
+            assertTrue(mManager.setDynamicShortcuts(list(s1)));
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+            mManager.removeAllDynamicShortcuts();
+
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllNotPinned()
+                    .areAllWithNoIntent();
+
+            // Accept the request -> should fail.
+            assertForLauncherCallbackNoThrow(mLauncherApps,
+                    () -> assertFalse(request.accept()))
+                    .assertNoCallbackCalled();
+        });
+
+        // Intent shouldn't be sent.
+        verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+    }
+
+    /**
+     * The dynamic shortcut existed, but before accepting(), it's removed.  Because the request
+     * has all the mandatory fields, we can go ahead and still publish it.
+     */
+    public void testRequestPinShortcut_dynamicExists_thenRemoved_okay() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            // Create dynamic shortcut
+            ShortcutInfo s1 = makeShortcut("s1");
+            assertTrue(mManager.setDynamicShortcuts(list(s1)));
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("s1", "new"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+            mManager.removeAllDynamicShortcuts();
+
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllNotPinned()
+                    .areAllWithNoIntent();
+
+            // Accept the request -> should fail.
+            assertTrue(request.accept());
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllFloating()
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("new", si.getShortLabel());
+                    });
+        });
+    }
+
+    /**
+     * The manifest shortcut existed, but before accepting(), it's removed.  Because the request
+     * has a partial shortcut, accept() should fail.
+     */
+    public void testRequestPinShortcut_manifestExists_thenRemoved_error() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+            publishManifestShortcutsAsCaller(R.xml.shortcut_0);
+
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllNotPinned()
+                    .areAllWithNoIntent();
+
+            // Accept the request -> should fail.
+            assertForLauncherCallbackNoThrow(mLauncherApps,
+                    () -> assertFalse(request.accept()))
+                    .assertNoCallbackCalled();
+        });
+
+        // Intent shouldn't be sent.
+        verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+    }
+
+    /**
+     * The manifest shortcut existed, but before accepting(), it's removed.  Because the request
+     * has all the mandatory fields, we can go ahead and still publish it.
+     */
+    public void testRequestPinShortcut_manifestExists_thenRemoved_okay() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutWithShortLabel("ms1", "new"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+            publishManifestShortcutsAsCaller(R.xml.shortcut_0);
+
+            assertWith(getCallerShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllNotPinned()
+                    .areAllWithNoIntent();
+
+
+            // Accept the request -> should fail.
+            assertTrue(request.accept());
+        });
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1")
+                    .areAllMutable() // Note it's no longer immutable.
+                    .areAllFloating()
+                    .forShortcutWithId("ms1", si -> {
+                        assertEquals("new", si.getShortLabel());
+                    });
+        });
+    }
+
+    /**
+     * The dynamic shortcut existed, but before accepting(), it's removed.  Because the request
+     * has a partial shortcut, accept() should fail.
+     */
+    public void testRequestPinShortcut_dynamicExists_thenDisabled_error() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            ShortcutInfo s1 = makeShortcut("s1");
+            assertTrue(mManager.setDynamicShortcuts(list(s1)));
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("s1"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+        });
+
+        // Then, pin by another launcher and disable it.
+        // We have to pin it here so that disable() won't remove it.
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0));
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_P0);
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            mManager.disableShortcuts(list("s1"));
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllDisabled();
+        });
+
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("s1")
+                    .areAllDynamic()
+                    .areAllNotPinned()
+                    .areAllWithNoIntent();
+
+            // Accept the request -> should fail.
+            assertForLauncherCallbackNoThrow(mLauncherApps,
+                    () -> assertFalse(request.accept()))
+                    .assertNoCallbackCalled();
+
+            // Note s1 is floating and pinned by another launcher, so it shouldn't be
+            // visible here.
+            assertWith(getShortcutAsLauncher(USER_P0))
+                    .isEmpty();
+        });
+
+        // Intent shouldn't be sent.
+        verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("s1")
+                    .areAllDisabled();
+        });
+    }
+
+    /**
+     * The manifest shortcut existed, but before accepting(), it's removed.  Because the request
+     * has a partial shortcut, accept() should fail.
+     */
+    public void testRequestPinShortcut_manifestExists_thenDisabled_error() {
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            publishManifestShortcutsAsCaller(R.xml.shortcut_1);
+
+            assertTrue(mManager.requestPinShortcut(makeShortcutIdOnly("ms1"),
+                    /* resultIntent=*/ null));
+
+            verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+        });
+
+        // Then, pin by another launcher and disable it.
+        // We have to pin it here so that disable() won't remove it.
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_2, USER_0));
+        runWithCaller(LAUNCHER_2, USER_0, () -> {
+            mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("ms1"), HANDLE_USER_P0);
+        });
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            publishManifestShortcutsAsCaller(R.xml.shortcut_0);
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1")
+                    .areAllDisabled();
+        });
+
+        setDefaultLauncher(USER_0, mMainActivityFetcher.apply(LAUNCHER_1, USER_0));
+        runWithCaller(LAUNCHER_1, USER_0, () -> {
+            // Check the intent passed to startActivityAsUser().
+            final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
+
+            verify(mServiceContext).startActivityAsUser(intent.capture(), eq(HANDLE_USER_0));
+
+            assertPinItemRequestIntent(intent.getValue(), mInjectedClientPackage);
+
+            // Check the request object.
+            final PinItemRequest request = mLauncherApps.getPinItemRequest(intent.getValue());
+
+            assertPinItemRequest(request);
+
+            assertWith(request.getShortcutInfo())
+                    .haveIds("ms1")
+                    .areAllManifest()
+                    .areAllNotPinned()
+                    .areAllWithNoIntent();
+
+            // Accept the request -> should fail.
+            assertForLauncherCallbackNoThrow(mLauncherApps,
+                    () -> assertFalse(request.accept()))
+                    .assertNoCallbackCalled();
+
+            // Note ms1 is floating and pinned by another launcher, so it shouldn't be
+            // visible here.
+            assertWith(getShortcutAsLauncher(USER_P0))
+                    .isEmpty();
+        });
+
+        // Intent shouldn't be sent.
+        verify(mServiceContext, times(0)).sendIntentSender(any(IntentSender.class));
+
+        runWithCaller(CALLING_PACKAGE_1, USER_P0, () -> {
+            assertWith(getCallerShortcuts())
+                    .haveIds("ms1")
+                    .areAllDisabled();
+        });
+    }
+
     // TODO More tests:
-    // Shortcut exists as a dynamic shortcut.
-    // Shortcut exists as a manifest shortcut.
-    // Shortcut exists as a dynamic, already pinned by this launcher
-    // Shortcut exists as a manifest, already pinned by this launcher
-    // Shortcut exists as floating, already pinned by this launcher
-
-    // Shortcut exists as a dynamic, already pinned by another launcher
-    // Shortcut exists as a manifest, already pinned by another launcher
-    // Shortcut exists as floating, already pinned by another launcher
-
-    // Shortcut exists but disabled (both mutable and immutable)
-
-    // Shortcut exists but removed before accept().
-    // Shortcut exists but disabled before accept().
-    // Shortcut exists but pinned before accept().
-    // Shortcut exists but unpinned before accept().
 
     // Cancel previous pending request and release memory?
+
+    // Check the launcher callback too.
+
+    // Missing fields -- pre and post, both.
 }
diff --git a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
index 8ecea71..a664f21 100644
--- a/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
+++ b/services/tests/shortcutmanagerutils/src/com/android/server/pm/shortcutmanagertest/ShortcutManagerTestUtils.java
@@ -934,6 +934,16 @@
             return this;
         }
 
+        public ShortcutListAsserter areAllWithIntent() {
+            forAllShortcuts(s -> assertNotNull("id=" + s.getId(), s.getIntent()));
+            return this;
+        }
+
+        public ShortcutListAsserter areAllWithNoIntent() {
+            forAllShortcuts(s -> assertNull("id=" + s.getId(), s.getIntent()));
+            return this;
+        }
+
         public ShortcutListAsserter forAllShortcuts(Consumer<ShortcutInfo> sa) {
             boolean found = false;
             for (int i = 0; i < mList.size(); i++) {
@@ -1092,6 +1102,16 @@
         return asserter;
     }
 
+    public static LauncherCallbackAsserter assertForLauncherCallbackNoThrow(
+            LauncherApps launcherApps, Runnable body) {
+        try {
+            return assertForLauncherCallback(launcherApps, body);
+        } catch (InterruptedException e) {
+            fail("Caught InterruptedException");
+            return null; // Never happens.
+        }
+    }
+
     public static void retryUntil(BooleanSupplier checker, String message) {
         retryUntil(checker, message, 30);
     }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 939a3b8..8f2a44c 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1154,6 +1154,17 @@
     public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL =
         "support_3gpp_call_forwarding_while_roaming_bool";
 
+    /**
+     * Determine whether user edited tether APN (type dun) has effect
+     * {@code false} - Default. APN with dun type in telephony database has no effect.
+     *
+     * {@code true}  - DUN APN added/edited in ApnEditor will be used for tethering data call.
+     *
+     * @hide
+     */
+    public static final String KEY_EDITABLE_TETHER_APN_BOOL =
+            "editable_tether_apn_bool";
+
     /** The default value for every variable. */
     private final static PersistableBundle sDefaults;
 
@@ -1358,6 +1369,7 @@
         sDefaults.putStringArray(KEY_CARRIER_WIFI_STRING_ARRAY, null);
         sDefaults.putInt(KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT, -1);
         sDefaults.putBoolean(KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL, true);
+        sDefaults.putBoolean(KEY_EDITABLE_TETHER_APN_BOOL, false);
     }
 
     /**
diff --git a/tools/incident_report/Android.mk b/tools/incident_report/Android.mk
new file mode 100644
index 0000000..ed89bd6
--- /dev/null
+++ b/tools/incident_report/Android.mk
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+# ==========================================================
+# Build the host executable: protoc-gen-javastream
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := incident_report
+
+LOCAL_C_INCLUDES := \
+    external/protobuf/src
+
+LOCAL_SRC_FILES := \
+    generic_message.cpp \
+    main.cpp \
+    printer.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+    libplatformprotos \
+    libprotobuf-cpp-full
+
+include $(BUILD_HOST_EXECUTABLE)
+
+
diff --git a/tools/incident_report/formatter.cpp b/tools/incident_report/formatter.cpp
new file mode 100644
index 0000000..944348f
--- /dev/null
+++ b/tools/incident_report/formatter.cpp
@@ -0,0 +1,89 @@
+/*
+ * 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 "proto_format.h"
+
+#include <string.h>
+
+extern int const PROTO_FORMAT_STRING_POOL_SIZE;
+extern int const PROTO_FORMAT_ENUM_LABELS_LENGTH;
+extern int const PROTO_FORMAT_MESSAGES_LENGTH;
+extern int const PROTO_FORMAT_FIELDS_LENGTH;
+
+extern char const PROTO_FORMAT_STRING_POOL[];
+extern ProtoFieldFormat const PROTO_FORMAT_FIELDS[];
+extern ProtoEnumLabel const PROTO_FORMAT_ENUM_LABELS[];
+extern ProtoMessageFormat const PROTO_FORMAT_MESSAGES[];
+
+static const char*
+get_string(int index)
+{
+    if (index >= 0 && index < PROTO_FORMAT_STRING_POOL_SIZE) {
+        return PROTO_FORMAT_STRING_POOL + index;
+    } else {
+        // These indices all come from within the generated table, so just crash now.
+        *(int*)NULL = 42;
+        return NULL;
+    }
+}
+
+static ProtoMessageFormat const*
+get_message(int index)
+{
+    if (index >= 0 && index < PROTO_FORMAT_MESSAGES_LENGTH) {
+        return PROTO_FORMAT_MESSAGES + index;
+    } else {
+        // These indices all come from within the generated table, so just crash now.
+        *(int*)NULL = 42;
+        return NULL;
+    }
+}
+
+static int
+compare_name(const char* full, const char* package, const char* clazz)
+{
+    int const packageLen = strlen(package);
+    int cmp = strncmp(full, package, packageLen);
+    if (cmp == 0) {
+        cmp = full[packageLen] - '.';
+        if (cmp == 0) {
+            return strcmp(full + packageLen, clazz);
+        }
+    }
+    return cmp;
+}
+
+int
+find_message_index(const char* name)
+{
+    size_t low = 0;
+    size_t high = PROTO_FORMAT_FIELDS_LENGTH - 1;
+
+    while (low <= high) {
+        size_t mid = (low + high) >> 1;
+        ProtoMessageFormat const* msg = get_message(mid);
+
+        int cmp = compare_name(name, get_string(msg->package_name), get_string(msg->package_name));
+        if (cmp < 0) {
+            low = mid + 1;
+        } else if (cmp > 0) {
+            high = mid - 1;
+        } else {
+            return mid;
+        }
+    }
+    return -1;
+}
diff --git a/tools/incident_report/generic_message.cpp b/tools/incident_report/generic_message.cpp
new file mode 100644
index 0000000..84d9d7c
--- /dev/null
+++ b/tools/incident_report/generic_message.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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 "generic_message.h"
+
+GenericMessage::GenericMessage()
+{
+}
+
+GenericMessage::~GenericMessage()
+{
+}
+
+void
+GenericMessage::addInt32(int32_t fieldId, uint32_t value)
+{
+    Node node;
+    node.type = TYPE_VALUE32;
+    node.value32 = value;
+    mNodes.insert(pair<int32_t,Node>(fieldId, node));
+}
+
+void
+GenericMessage::addInt64(int32_t fieldId, uint64_t value)
+{
+    Node node;
+    node.type = TYPE_VALUE64;
+    node.value64 = value;
+    mNodes.insert(pair<int32_t,Node>(fieldId, node));
+}
+
+GenericMessage*
+GenericMessage::addMessage(int32_t fieldId)
+{
+    GenericMessage* result = new GenericMessage();
+    Node node;
+    node.type = TYPE_MESSAGE;
+    node.message = result;
+    mNodes.insert(pair<int32_t,Node>(fieldId, node));
+    return result;
+}
+
+void
+GenericMessage::addString(int32_t fieldId, const string& value)
+{
+    Node node;
+    node.type = TYPE_STRING;
+    node.str = new string(value);
+    mNodes.insert(pair<int32_t,Node>(fieldId, node));
+}
+
+GenericMessage::const_iterator_pair
+GenericMessage::find(int fieldId) const
+{
+    return mNodes.equal_range(fieldId);
+}
+
diff --git a/tools/incident_report/generic_message.h b/tools/incident_report/generic_message.h
new file mode 100644
index 0000000..df3f7b2
--- /dev/null
+++ b/tools/incident_report/generic_message.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+#ifndef GENERIC_MESSAGE_H
+#define GENERIC_MESSAGE_H
+
+#include <map>
+#include <string>
+
+using namespace std;
+
+/**
+ * Class to represent a protobuf Message, where we don't actually
+ * know what any of the fields are, just their type codes. In other
+ * words, this loslessly stores a parsed protobuf object without
+ * having the .proto file that generated it.
+ */
+class GenericMessage
+{
+public:
+    GenericMessage();
+    ~GenericMessage();
+
+    enum {
+        TYPE_VALUE32,
+        TYPE_VALUE64,
+        TYPE_MESSAGE,
+        TYPE_STRING,
+        TYPE_DATA
+    };
+
+    struct Node {
+        uint32_t type;
+        union {
+            uint32_t value32;
+            uint64_t value64;
+            GenericMessage* message;
+            string* str;
+            string* data;
+        };
+    };
+
+    void addInt32(int32_t fieldId, uint32_t value);
+    void addInt64(int32_t fieldId, uint64_t value);
+    GenericMessage* addMessage(int32_t fieldId);
+    void addString(int32_t fieldId, const string& value);
+
+    typedef multimap<int32_t,Node>::const_iterator const_iterator;
+    typedef pair<const_iterator,const_iterator> const_iterator_pair;
+
+    const_iterator_pair find(int fieldId) const;
+
+private:
+    multimap<int,Node> mNodes;
+};
+
+#endif // GENERIC_MESSAGE_H
+
diff --git a/tools/incident_report/main.cpp b/tools/incident_report/main.cpp
new file mode 100644
index 0000000..a814847
--- /dev/null
+++ b/tools/incident_report/main.cpp
@@ -0,0 +1,576 @@
+/*
+ * 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 "generic_message.h"
+#include "printer.h"
+
+#include <frameworks/base/core/proto/android/os/incident_proto.pb.h>
+#include <google/protobuf/wire_format.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+using namespace android::os;
+using namespace google::protobuf;
+using namespace google::protobuf::io;
+using namespace google::protobuf::internal;
+
+static bool read_message(CodedInputStream* in, Descriptor const* descriptor,
+        GenericMessage* message);
+static void print_message(Out* out, Descriptor const* descriptor, GenericMessage const* message);
+
+// ================================================================================
+static bool
+read_length_delimited(CodedInputStream* in, uint32 fieldId, Descriptor const* descriptor,
+        GenericMessage* message)
+{
+    uint32 size;
+    if (!in->ReadVarint32(&size)) {
+        return false;
+    }
+
+    FieldDescriptor const* field = descriptor->FindFieldByNumber(fieldId);
+    if (field != NULL) {
+        int type = field->type();
+        if (type == FieldDescriptor::TYPE_MESSAGE) {
+            GenericMessage* child = message->addMessage(fieldId);
+
+            CodedInputStream::Limit limit = in->PushLimit(size);
+            bool rv = read_message(in, field->message_type(), child);
+            in->PopLimit(limit);
+            return rv;
+        } else if (type == FieldDescriptor::TYPE_STRING) {
+            // TODO: do a version of readstring that just pumps the data
+            // rather than allocating a string which we don't care about.
+            string str;
+            if (in->ReadString(&str, size)) {
+                message->addString(fieldId, str);
+                return true;
+            } else {
+                return false;
+            }
+        } else if (type == FieldDescriptor::TYPE_BYTES) {
+            // TODO: Save bytes field.
+            return in->Skip(size);
+        }
+    }
+    return in->Skip(size);
+}
+
+// ================================================================================
+static bool
+read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message)
+{
+    uint32 value32;
+    uint64 value64;
+
+    while (true) {
+        uint32 tag = in->ReadTag();
+        if (tag == 0) {
+            return true;
+        }
+        int fieldId = WireFormatLite::GetTagFieldNumber(tag);
+        switch (WireFormatLite::GetTagWireType(tag)) {
+            case WireFormatLite::WIRETYPE_VARINT:
+                if (in->ReadVarint64(&value64)) {
+                    message->addInt64(fieldId, value64);
+                    break;
+                } else {
+                    return false;
+                }
+            case WireFormatLite::WIRETYPE_FIXED64:
+                if (in->ReadLittleEndian64(&value64)) {
+                    message->addInt64(fieldId, value64);
+                    break;
+                } else {
+                    return false;
+                }
+            case WireFormatLite::WIRETYPE_LENGTH_DELIMITED:
+                if (!read_length_delimited(in, fieldId, descriptor, message)) {
+                    return false;
+                }
+                break;
+            case WireFormatLite::WIRETYPE_FIXED32:
+                if (in->ReadLittleEndian32(&value32)) {
+                    message->addInt32(fieldId, value32);
+                    break;
+                } else {
+                    return false;
+                }
+            default:
+                fprintf(stderr, "bad tag: 0x%x (%d) at index %d\n", tag, tag,
+                        in->CurrentPosition());
+                return false;
+        }
+    }
+}
+
+// ================================================================================
+static void
+print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& node)
+{
+    uint32_t val32;
+    FieldDescriptor::Type type = field->type();
+
+    switch (node.type) {
+        case GenericMessage::TYPE_VALUE32:
+            switch (type) {
+                case FieldDescriptor::TYPE_FIXED32:
+                    out->printf("%u", node.value32);
+                    break;
+                case FieldDescriptor::TYPE_SFIXED32:
+                    out->printf("%d", node.value32);
+                    break;
+                case FieldDescriptor::TYPE_FLOAT:
+                    out->printf("%f", *(float*)&node.value32);
+                    break;
+                default:
+                    out->printf("(unexpected value %d (0x%x)", node.value32, node.value32);
+                    break;
+            }
+            break;
+        case GenericMessage::TYPE_VALUE64:
+            switch (type) {
+                case FieldDescriptor::TYPE_FIXED64:
+                case FieldDescriptor::TYPE_SFIXED64:
+                case FieldDescriptor::TYPE_DOUBLE:
+                    out->printf("%f", *(double*)&node.value64);
+                    break;
+                case FieldDescriptor::TYPE_SINT32:
+                case FieldDescriptor::TYPE_INT32:
+                    val32 = (uint32_t)node.value32;
+                    out->printf("%d", val32);
+                    break;
+                case FieldDescriptor::TYPE_INT64:
+                case FieldDescriptor::TYPE_UINT32:
+                    val32 = (uint32_t)node.value32;
+                    out->printf("%u", val32);
+                    break;
+                case FieldDescriptor::TYPE_UINT64:
+                case FieldDescriptor::TYPE_SINT64:
+                case FieldDescriptor::TYPE_BOOL:
+                    if (node.value64) {
+                        out->printf("true");
+                    } else {
+                        out->printf("false");
+                    }
+                    break;
+                case FieldDescriptor::TYPE_ENUM:
+                default:
+                    out->printf("(unexpected value %ld (0x%x))", node.value64, node.value64);
+                    break;
+            }
+            break;
+        case GenericMessage::TYPE_MESSAGE:
+            print_message(out, field->message_type(), node.message);
+            break;
+        case GenericMessage::TYPE_STRING:
+            // TODO: custom format for multi-line strings.
+            out->printf("%s", node.str->c_str());
+            break;
+        case GenericMessage::TYPE_DATA:
+            out->printf("<bytes>");
+            break;
+    }
+}
+
+static void
+print_message(Out* out, Descriptor const* descriptor, GenericMessage const* message)
+{
+    out->printf("%s {\n", descriptor->name().c_str());
+    out->indent();
+
+    int const N = descriptor->field_count();
+    for (int i=0; i<N; i++) {
+        FieldDescriptor const* field = descriptor->field(i);
+
+        int fieldId = field->number();
+        bool repeated = field->label() == FieldDescriptor::LABEL_REPEATED;
+        FieldDescriptor::Type type = field->type();
+        GenericMessage::const_iterator_pair it = message->find(fieldId);
+
+        out->printf("%s=", field->name().c_str());
+        if (repeated) {
+            if (it.first != it.second) {
+                out->printf("[");
+                if (type == FieldDescriptor::TYPE_MESSAGE
+                        || type == FieldDescriptor::TYPE_STRING
+                        || type == FieldDescriptor::TYPE_BYTES) {
+                    out->printf("\n");
+                }
+                out->indent();
+
+                for (GenericMessage::const_iterator_pair it = message->find(fieldId);
+                        it.first != it.second; it.first++) {
+                    print_value(out, field, it.first->second);
+                    if (type == FieldDescriptor::TYPE_MESSAGE
+                            || type == FieldDescriptor::TYPE_STRING
+                            || type == FieldDescriptor::TYPE_BYTES) {
+                        out->printf("\n");
+                    }
+                }
+
+                out->dedent();
+                out->printf("]");
+            } else {
+                out->printf("[]");
+            }
+        } else {
+            if (it.first != it.second) {
+                print_value(out, field, it.first->second);
+            } else {
+                switch (type) {
+                    case FieldDescriptor::TYPE_BOOL:
+                        out->printf("false");
+                        break;
+                    case FieldDescriptor::TYPE_STRING:
+                    case FieldDescriptor::TYPE_MESSAGE:
+                        out->printf("");
+                        break;
+                    case FieldDescriptor::TYPE_ENUM:
+                        out->printf("%s", field->default_value_enum()->name().c_str());
+                        break;
+                    default:
+                        out->printf("0");
+                        break;
+                }
+            }
+        }
+        out->printf("\n");
+    }
+    out->dedent();
+    out->printf("}");
+}
+
+// ================================================================================
+static uint8_t*
+write_raw_varint(uint8_t* buf, uint32_t val)
+{
+    uint8_t* p = buf;
+    while (true) {
+        if ((val & ~0x7F) == 0) {
+            *p++ = (uint8_t)val;
+            return p;
+        } else {
+            *p++ = (uint8_t)((val & 0x7F) | 0x80);
+            val >>= 7;
+        }
+    }
+}
+
+static int
+write_all(int fd, uint8_t const* buf, size_t size)
+{
+    while (size > 0) {
+        ssize_t amt = ::write(fd, buf, size);
+        if (amt < 0) {
+            return errno;
+        }
+        size -= amt;
+        buf += amt;
+    }
+    return 0;
+}
+
+static int
+adb_incident_workaround(const char* adbSerial, const vector<string>& sections)
+{
+    const int maxAllowedSize = 20 * 1024 * 1024; // 20MB
+    uint8_t* buffer = (uint8_t*)malloc(maxAllowedSize);
+
+    for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) {
+        Descriptor const* descriptor = IncidentProto::descriptor();
+        FieldDescriptor const* field;
+
+        // Get the name and field id.
+        string name = *it;
+        char* end;
+        int id = strtol(name.c_str(), &end, 0);
+        if (*end == '\0') {
+            // If it's an id, find out the string.
+            field = descriptor->FindFieldByNumber(id);
+            if (field == NULL) {
+                fprintf(stderr, "Unable to find field number: %d\n", id);
+                return 1;
+            }
+            name = field->name();
+        } else {
+            // If it's a string, find out the id.
+            field = descriptor->FindFieldByName(name);
+            if (field == NULL) {
+                fprintf(stderr, "Unable to find field: %s\n", name.c_str());
+                return 1;
+            }
+            id = field->number();
+        }
+        
+        int pfd[2];
+        if (pipe(pfd) != 0) {
+            fprintf(stderr, "pipe failed: %s\n", strerror(errno));
+            return 1;
+        }
+
+        pid_t pid = fork();
+        if (pid == -1) {
+            fprintf(stderr, "fork failed: %s\n", strerror(errno));
+            return 1;
+        } else if (pid == 0) {
+            // child
+            dup2(pfd[1], STDOUT_FILENO);
+            close(pfd[0]);
+            close(pfd[1]);
+
+            char const** args = (char const**)malloc(sizeof(char*) * 8);
+            int argpos = 0;
+            args[argpos++] = "adb";
+            if (adbSerial != NULL) {
+                args[argpos++] = "-s";
+                args[argpos++] = adbSerial;
+            }
+            args[argpos++] = "shell";
+            args[argpos++] = "dumpsys";
+            args[argpos++] = name.c_str();
+            args[argpos++] = "--proto";
+            args[argpos++] = NULL;
+            execvp(args[0], (char*const*)args);
+            fprintf(stderr, "execvp failed: %s\n", strerror(errno));
+            return 1;
+        } else {
+            // parent
+            close(pfd[1]);
+
+            size_t size = 0;
+            while (size < maxAllowedSize) {
+                ssize_t amt = read(pfd[0], buffer + size, maxAllowedSize - size);
+                if (amt == 0) {
+                    break;
+                } else if (amt == -1) {
+                    fprintf(stderr, "read error: %s\n", strerror(errno));
+                    return 1;
+                }
+                size += amt;
+            }
+
+            int status;
+            do {
+                waitpid(pid, &status, 0);
+            } while (!WIFEXITED(status));
+            if (WEXITSTATUS(status) != 0) {
+                return WEXITSTATUS(status);
+            }
+
+            if (size > 0) {
+                uint8_t header[20];
+                uint8_t* p = write_raw_varint(header, (id << 3) | 2);
+                p = write_raw_varint(p, size);
+                int err = write_all(STDOUT_FILENO, header, p-header);
+                if (err != 0) {
+                    fprintf(stderr, "write error: %s\n", strerror(err));
+                    return 1;
+                }
+                err = write_all(STDOUT_FILENO, buffer, size);
+                if (err != 0) {
+                    fprintf(stderr, "write error: %s\n", strerror(err));
+                    return 1;
+                }
+            }
+
+            close(pfd[0]);
+        }
+    }
+
+    return 0;
+}
+
+// ================================================================================
+static void
+usage(FILE* out)
+{
+    fprintf(out, "usage: incident_report -i INPUT [-o OUTPUT]\n");
+    fprintf(out, "\n");
+    fprintf(out, "Pretty-prints an incident report protobuf file.\n");
+    fprintf(out, "  -i INPUT    the input file. INPUT may be '-' to use stdin\n");
+    fprintf(out, "  -o OUTPUT   the output file. OUTPUT may be '-' or omitted to use stdout\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
+    fprintf(out, "usage: incident_report [-o OUTPUT] [-t|b] [-s SERIAL] [SECTION...]\n");
+    fprintf(out, "\n");
+    fprintf(out, "Take an incident report over adb (which must be in the PATH).\n");
+    fprintf(out, "  -b          output the incident report raw protobuf format\n");
+    fprintf(out, "  -o OUTPUT   the output file. OUTPUT may be '-' or omitted to use stdout\n");
+    fprintf(out, "  -s SERIAL   sent to adb to choose which device, instead of $ANDROID_SERIAL\n");
+    fprintf(out, "  -t          output the incident report in pretty-printed text format\n");
+    fprintf(out, "\n");
+    fprintf(out, "  SECTION     which bugreport sections to print, either the int code of the\n");
+    fprintf(out, "              section in the Incident proto or the field name.  If ommited,\n");
+    fprintf(out, "              the report will contain all fields\n");
+    fprintf(out, "\n");
+}
+
+int
+main(int argc, char** argv)
+{
+    enum { OUTPUT_TEXT, OUTPUT_PROTO } outputFormat = OUTPUT_TEXT;
+    const char* inFilename = NULL;
+    const char* outFilename = NULL;
+    const char* adbSerial = NULL;
+    bool adbIncidentWorkaround = true;
+    pid_t childPid = -1;
+    vector<string> sections;
+
+    int opt;
+    while ((opt = getopt(argc, argv, "bhi:o:s:tw")) != -1) {
+        switch (opt) {
+            case 'b':
+                outputFormat = OUTPUT_PROTO;
+                break;
+            case 'i':
+                inFilename = optarg;
+                break;
+            case 'o':
+                outFilename = optarg;
+                break;
+            case 's':
+                adbSerial = optarg;
+                break;
+            case 't':
+                outputFormat = OUTPUT_TEXT;
+                break;
+            case 'h':
+                usage(stdout);
+                return 0;
+            case 'w':
+                adbIncidentWorkaround = false;
+                break;
+            default:
+                usage(stderr);
+                return 1;
+        }
+    }
+
+    while (optind < argc) {
+        sections.push_back(argv[optind++]);
+    }
+
+    int inFd;
+    if (inFilename != NULL) {
+        // translate-only mode - oepn the file or use stdin.
+        if (strcmp("-", inFilename) == 0) {
+            inFd = STDIN_FILENO;
+        } else {
+            inFd = open(inFilename, O_RDONLY | O_CLOEXEC);
+            if (inFd < 0) {
+                fprintf(stderr, "unable to open file for read (%s): %s\n", strerror(errno),
+                        inFilename);
+                return 1;
+            }
+        }
+    } else {
+        // pipe mode - run adb shell incident ...
+        int pfd[2];
+        if (pipe(pfd) != 0) {
+            fprintf(stderr, "pipe failed: %s\n", strerror(errno));
+            return 1;
+        }
+
+        childPid = fork();
+        if (childPid == -1) {
+            fprintf(stderr, "fork failed: %s\n", strerror(errno));
+            return 1;
+        } else if (childPid == 0) {
+            dup2(pfd[1], STDOUT_FILENO);
+            close(pfd[0]);
+            close(pfd[1]);
+            // child
+            if (adbIncidentWorkaround) {
+                // TODO: Until the device side incident command is checked in,
+                // the incident_report builds the outer Incident proto by hand
+                // from individual adb shell dumpsys <service> --proto calls,
+                // with a maximum allowed output size.
+                return adb_incident_workaround(adbSerial, sections);
+            }
+
+            // TODO: This is what the real implementation will be...
+            char const** args = (char const**)malloc(sizeof(char*) * (6 + sections.size()));
+            int argpos = 0;
+            args[argpos++] = "adb";
+            if (adbSerial != NULL) {
+                args[argpos++] = "-s";
+                args[argpos++] = adbSerial;
+            }
+            args[argpos++] = "shell";
+            args[argpos++] = "incident";
+            for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) {
+                args[argpos++] = it->c_str();
+            }
+            args[argpos++] = NULL;
+            execvp(args[0], (char*const*)args);
+            fprintf(stderr, "execvp failed: %s\n", strerror(errno));
+            return 0;
+        } else {
+            // parent
+            inFd = pfd[0];
+            close(pfd[1]);
+        }
+    }
+
+    int outFd;
+    if (outFilename == NULL || strcmp("-", outFilename) == 0) {
+        outFd = STDOUT_FILENO;
+    } else {
+        outFd = open(outFilename, O_CREAT | O_RDWR, 0666);
+        if (outFd < 0) {
+            fprintf(stderr, "unable to open file for write: %s\n", outFilename);
+            return 1;
+        }
+    }
+
+    GenericMessage message;
+
+    Descriptor const* descriptor = IncidentProto::descriptor();
+    FileInputStream infile(inFd);
+    CodedInputStream in(&infile);
+
+    if (!read_message(&in, descriptor, &message)) {
+        fprintf(stderr, "unable to read incident\n");
+        return 1;
+    }
+
+    Out out(outFd);
+
+    print_message(&out, descriptor, &message);
+    out.printf("\n");
+
+    if (childPid != -1) {
+        int status;
+        do {
+            waitpid(childPid, &status, 0);
+        } while (!WIFEXITED(status));
+        if (WEXITSTATUS(status) != 0) {
+            return WEXITSTATUS(status);
+        }
+    }
+
+    return 0;
+}
diff --git a/tools/incident_report/printer.cpp b/tools/incident_report/printer.cpp
new file mode 100644
index 0000000..8111b27
--- /dev/null
+++ b/tools/incident_report/printer.cpp
@@ -0,0 +1,128 @@
+/*
+ * 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 "printer.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#define INITIAL_BUF_SIZE (16*1024)
+
+char const* SPACES = "                                                            ";
+const int SPACE_COUNT = strlen(SPACES);
+
+Out::Out(int fd)
+    :mOut(fd == STDOUT_FILENO ? stdout : fdopen(fd, "w")),
+     mBufSize(INITIAL_BUF_SIZE),
+     mBuf((char*)malloc(INITIAL_BUF_SIZE)),
+     mIndent(0),
+     mPendingIndent(false)
+{
+}
+
+Out::~Out()
+{
+    fclose(mOut);
+}
+
+int
+Out::reallocate(int size)
+{
+    if (size > mBufSize) {
+        char* p = (char*)malloc(size);
+        if (p != NULL) {
+            free(mBuf);
+            mBufSize = size;
+            mBuf = p;
+            return size;
+        }
+    }
+    return mBufSize;
+}
+
+void
+Out::printf(const char* format, ...)
+{
+    if (mPendingIndent) {
+        print_indent();
+        mPendingIndent = false;
+    }
+
+    int len;
+
+    va_list args;
+    va_start(args, format);
+
+    len = vsnprintf(mBuf, mBufSize, format, args);
+    bool truncated = (len >= mBufSize) && (reallocate(len) < len);
+    
+    len = vsnprintf(mBuf, mBufSize, format, args);
+    va_end(args);
+
+    if (len > 0) {
+        if (mIndent == 0) {
+            fwrite(mBuf, len, 1, mOut);
+        } else {
+            char* last = mBuf;
+            char* p;
+            do {
+                p = strchr(last, '\n');
+                int size = p != NULL ? p - last + 1 : strlen(last);
+                fwrite(last, size, 1, mOut);
+                if (p != NULL) {
+                    if (p[1] == '\0') {
+                        mPendingIndent = true;
+                    } else {
+                        print_indent();
+                    }
+                }
+                last = p+1;
+            } while (p != NULL);
+        }
+    }
+}
+
+void
+Out::indent()
+{
+    mPendingIndent = true;
+    mIndent += 2;
+}
+
+void
+Out::dedent()
+{
+    if (mIndent > 0) {
+        mIndent -= 2;
+    }
+}
+
+void
+Out::print_indent()
+{
+#if 0
+    fprintf(mOut, "[%d]", mIndent);
+#else
+    int indent = mIndent;
+    while (indent > SPACE_COUNT) {
+        fwrite(SPACES, SPACE_COUNT, 1, mOut);
+        indent -= SPACE_COUNT;
+    }
+    fwrite(SPACES + SPACE_COUNT - indent, indent, 1, mOut);
+#endif
+}
diff --git a/tools/incident_report/printer.h b/tools/incident_report/printer.h
new file mode 100644
index 0000000..ed93fa1
--- /dev/null
+++ b/tools/incident_report/printer.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+#ifndef PRINTER_H
+#define PRINTER_H
+
+#include <stdio.h>
+
+class Out
+{
+public:
+    Out(int fd);
+    ~Out();
+
+    void printf(const char* format, ...);
+
+    void indent();
+    void dedent();
+
+private:
+    FILE* mOut;
+    int mBufSize;
+    char* mBuf;
+    int mIndent;
+    bool mPendingIndent;
+
+    int reallocate(int size);
+    void print_indent();
+};
+
+#endif // PRINTER_H
diff --git a/tools/incident_section_gen/Android.mk b/tools/incident_section_gen/Android.mk
new file mode 100644
index 0000000..acf3f83
--- /dev/null
+++ b/tools/incident_section_gen/Android.mk
@@ -0,0 +1,35 @@
+#
+# 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+# ==========================================================
+# Build the host executable: protoc-gen-javastream
+# ==========================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := incident-section-gen
+LOCAL_CFLAGS += -g -O0
+LOCAL_C_INCLUDES := \
+    external/protobuf/src
+LOCAL_SRC_FILES := \
+    main.cpp
+LOCAL_LDFLAGS := -ldl
+LOCAL_SHARED_LIBRARIES := \
+    libplatformprotos \
+    libprotobuf-cpp-full
+
+include $(BUILD_HOST_EXECUTABLE)
+
diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp
new file mode 100644
index 0000000..d004810
--- /dev/null
+++ b/tools/incident_section_gen/main.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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 <frameworks/base/core/proto/android/os/incident_proto.pb.h>
+
+
+#include <map>
+
+using namespace android::os;
+using namespace google::protobuf;
+using namespace google::protobuf::io;
+using namespace google::protobuf::internal;
+using namespace std;
+
+int
+main(int, const char**)
+{
+    map<string,FieldDescriptor const*> sections;
+    int N;
+
+    printf("// Auto generated file. Do not modify\n");
+    printf("\n");
+    printf("#include \"incident_sections.h\"\n");
+    printf("\n");
+
+    Descriptor const* descriptor = IncidentProto::descriptor();
+    N = descriptor->field_count();
+    for (int i=0; i<N; i++) {
+        const FieldDescriptor* field = descriptor->field(i);
+        if (field->type() == FieldDescriptor::TYPE_MESSAGE) {
+            sections[field->name()] = field;
+        }
+    }
+
+    printf("IncidentSection const INCIDENT_SECTIONS[] = {\n");
+    N = sections.size();
+    int i = 0;
+    for (map<string,FieldDescriptor const*>::const_iterator it = sections.begin();
+            it != sections.end(); it++, i++) {
+        const FieldDescriptor* field = it->second;
+        printf("    { %d, \"%s\" }", field->number(), field->name().c_str());
+        if (i != N-1) {
+            printf(",\n");
+        } else {
+            printf("\n");
+        }
+    }
+    printf("};\n");
+
+    printf("const int INCIDENT_SECTION_COUNT = %d;\n", N);
+
+    return 0;
+}