Merge "Encode/Decode genres using CSV" into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index eb8befa..b9f932b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -198,7 +198,6 @@
 
   public static final class R.attr {
     ctor public R.attr();
-    field public static final int abiOverride = 16844054; // 0x1010516
     field public static final int absListViewStyle = 16842858; // 0x101006a
     field public static final int accessibilityEventTypes = 16843648; // 0x1010380
     field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -1365,6 +1364,7 @@
     field public static final deprecated int unfocusedMonthDateColor = 16843588; // 0x1010344
     field public static final int unselectedAlpha = 16843278; // 0x101020e
     field public static final int updatePeriodMillis = 16843344; // 0x1010250
+    field public static final int use32bitAbi = 16844054; // 0x1010516
     field public static final int useDefaultMargins = 16843641; // 0x1010379
     field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
     field public static final int useLevel = 16843167; // 0x101019f
@@ -22998,7 +22998,7 @@
     method public void notifyRecordingStopped(android.net.Uri);
     method public abstract void onConnect(android.net.Uri);
     method public abstract void onDisconnect();
-    method public abstract void onStartRecording();
+    method public abstract void onStartRecording(android.net.Uri);
     method public abstract void onStopRecording();
   }
 
@@ -23044,7 +23044,7 @@
     ctor public TvRecordingClient(android.content.Context, java.lang.String, android.media.tv.TvRecordingClient.RecordingCallback, android.os.Handler);
     method public void connect(java.lang.String, android.net.Uri);
     method public void disconnect();
-    method public void startRecording();
+    method public void startRecording(android.net.Uri);
     method public void stopRecording();
   }
 
@@ -34045,6 +34045,7 @@
 
   public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
     method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+    method public byte[] getAttestationChallenge();
     method public java.lang.String[] getBlockModes();
     method public java.util.Date getCertificateNotAfter();
     method public java.util.Date getCertificateNotBefore();
@@ -34069,6 +34070,7 @@
     ctor public KeyGenParameterSpec.Builder(java.lang.String, int);
     method public android.security.keystore.KeyGenParameterSpec build();
     method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec);
+    method public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
     method public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
     method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(java.util.Date);
     method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotBefore(java.util.Date);
@@ -40661,6 +40663,21 @@
     method public static android.view.FocusFinder getInstance();
   }
 
+  public final class FrameMetrics {
+    ctor public FrameMetrics(android.view.FrameMetrics);
+    method public long getMetric(int);
+    field public static final int ANIMATION_DURATION = 2; // 0x2
+    field public static final int COMMAND_ISSUE_DURATION = 6; // 0x6
+    field public static final int DRAW_DURATION = 4; // 0x4
+    field public static final int FIRST_DRAW_FRAME = 9; // 0x9
+    field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
+    field public static final int LAYOUT_MEASURE_DURATION = 3; // 0x3
+    field public static final int SWAP_BUFFERS_DURATION = 7; // 0x7
+    field public static final int SYNC_DURATION = 5; // 0x5
+    field public static final int TOTAL_DURATION = 8; // 0x8
+    field public static final int UNKNOWN_DELAY_DURATION = 0; // 0x0
+  }
+
   public abstract class FrameStats {
     ctor public FrameStats();
     method public final long getEndTimeNano();
@@ -43164,6 +43181,7 @@
     ctor public Window(android.content.Context);
     method public abstract void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
     method public void addFlags(int);
+    method public final void addFrameMetricsListener(android.view.Window.FrameMetricsListener, android.os.Handler);
     method public void clearFlags(int);
     method public abstract void closeAllPanels();
     method public abstract void closePanel(int);
@@ -43215,6 +43233,7 @@
     method public abstract boolean performContextMenuIdentifierAction(int, int);
     method public abstract boolean performPanelIdentifierAction(int, int, int);
     method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int);
+    method public final void removeFrameMetricsListener(android.view.Window.FrameMetricsListener);
     method public boolean requestFeature(int);
     method public abstract void restoreHierarchyState(android.os.Bundle);
     method public abstract android.os.Bundle saveHierarchyState();
@@ -43340,6 +43359,10 @@
     method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
   }
 
+  public static abstract interface Window.FrameMetricsListener {
+    method public abstract void onMetricsAvailable(android.view.Window, android.view.FrameMetrics, int);
+  }
+
   public static abstract interface Window.OnRestrictedCaptionAreaChangedListener {
     method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
   }
@@ -58016,8 +58039,11 @@
     method public static boolean equals(java.lang.Object, java.lang.Object);
     method public static int hash(java.lang.Object...);
     method public static int hashCode(java.lang.Object);
+    method public static boolean isNull(java.lang.Object);
+    method public static boolean nonNull(java.lang.Object);
     method public static T requireNonNull(T);
     method public static T requireNonNull(T, java.lang.String);
+    method public static T requireNonNull(T, java.util.function.Supplier<java.lang.String>);
     method public static java.lang.String toString(java.lang.Object);
     method public static java.lang.String toString(java.lang.Object, java.lang.String);
   }
@@ -59722,6 +59748,217 @@
 
 }
 
+package java.util.function {
+
+  public abstract interface BiConsumer {
+    method public abstract void accept(T, U);
+    method public default java.util.function.BiConsumer<T, U> andThen(java.util.function.BiConsumer<? super T, ? super U>);
+  }
+
+  public abstract interface BiFunction {
+    method public default java.util.function.BiFunction<T, U, V> andThen(java.util.function.Function<? super R, ? extends V>);
+    method public abstract R apply(T, U);
+  }
+
+  public abstract interface BiPredicate {
+    method public default java.util.function.BiPredicate<T, U> and(java.util.function.BiPredicate<? super T, ? super U>);
+    method public default java.util.function.BiPredicate<T, U> negate();
+    method public default java.util.function.BiPredicate<T, U> or(java.util.function.BiPredicate<? super T, ? super U>);
+    method public abstract boolean test(T, U);
+  }
+
+  public abstract interface BinaryOperator implements java.util.function.BiFunction {
+    method public static java.util.function.BinaryOperator<T> maxBy(java.util.Comparator<? super T>);
+    method public static java.util.function.BinaryOperator<T> minBy(java.util.Comparator<? super T>);
+  }
+
+  public abstract interface BooleanSupplier {
+    method public abstract boolean getAsBoolean();
+  }
+
+  public abstract interface Consumer {
+    method public abstract void accept(T);
+    method public default java.util.function.Consumer<T> andThen(java.util.function.Consumer<? super T>);
+  }
+
+  public abstract interface DoubleBinaryOperator {
+    method public abstract double applyAsDouble(double, double);
+  }
+
+  public abstract interface DoubleConsumer {
+    method public abstract void accept(double);
+    method public default java.util.function.DoubleConsumer andThen(java.util.function.DoubleConsumer);
+  }
+
+  public abstract interface DoubleFunction {
+    method public abstract R apply(double);
+  }
+
+  public abstract interface DoublePredicate {
+    method public default java.util.function.DoublePredicate and(java.util.function.DoublePredicate);
+    method public default java.util.function.DoublePredicate negate();
+    method public default java.util.function.DoublePredicate or(java.util.function.DoublePredicate);
+    method public abstract boolean test(double);
+  }
+
+  public abstract interface DoubleSupplier {
+    method public abstract double getAsDouble();
+  }
+
+  public abstract interface DoubleToIntFunction {
+    method public abstract int applyAsInt(double);
+  }
+
+  public abstract interface DoubleToLongFunction {
+    method public abstract long applyAsLong(double);
+  }
+
+  public abstract interface DoubleUnaryOperator {
+    method public default java.util.function.DoubleUnaryOperator andThen(java.util.function.DoubleUnaryOperator);
+    method public abstract double applyAsDouble(double);
+    method public default java.util.function.DoubleUnaryOperator compose(java.util.function.DoubleUnaryOperator);
+    method public static java.util.function.DoubleUnaryOperator identity();
+  }
+
+  public abstract interface Function {
+    method public default java.util.function.Function<T, V> andThen(java.util.function.Function<? super R, ? extends V>);
+    method public abstract R apply(T);
+    method public default java.util.function.Function<V, R> compose(java.util.function.Function<? super V, ? extends T>);
+    method public static java.util.function.Function<T, T> identity();
+  }
+
+  public abstract interface IntBinaryOperator {
+    method public abstract int applyAsInt(int, int);
+  }
+
+  public abstract interface IntConsumer {
+    method public abstract void accept(int);
+    method public default java.util.function.IntConsumer andThen(java.util.function.IntConsumer);
+  }
+
+  public abstract interface IntFunction {
+    method public abstract R apply(int);
+  }
+
+  public abstract interface IntPredicate {
+    method public default java.util.function.IntPredicate and(java.util.function.IntPredicate);
+    method public default java.util.function.IntPredicate negate();
+    method public default java.util.function.IntPredicate or(java.util.function.IntPredicate);
+    method public abstract boolean test(int);
+  }
+
+  public abstract interface IntSupplier {
+    method public abstract int getAsInt();
+  }
+
+  public abstract interface IntToDoubleFunction {
+    method public abstract double applyAsDouble(int);
+  }
+
+  public abstract interface IntToLongFunction {
+    method public abstract long applyAsLong(int);
+  }
+
+  public abstract interface IntUnaryOperator {
+    method public default java.util.function.IntUnaryOperator andThen(java.util.function.IntUnaryOperator);
+    method public abstract int applyAsInt(int);
+    method public default java.util.function.IntUnaryOperator compose(java.util.function.IntUnaryOperator);
+    method public static java.util.function.IntUnaryOperator identity();
+  }
+
+  public abstract interface LongBinaryOperator {
+    method public abstract long applyAsLong(long, long);
+  }
+
+  public abstract interface LongConsumer {
+    method public abstract void accept(long);
+    method public default java.util.function.LongConsumer andThen(java.util.function.LongConsumer);
+  }
+
+  public abstract interface LongFunction {
+    method public abstract R apply(long);
+  }
+
+  public abstract interface LongPredicate {
+    method public default java.util.function.LongPredicate and(java.util.function.LongPredicate);
+    method public default java.util.function.LongPredicate negate();
+    method public default java.util.function.LongPredicate or(java.util.function.LongPredicate);
+    method public abstract boolean test(long);
+  }
+
+  public abstract interface LongSupplier {
+    method public abstract long getAsLong();
+  }
+
+  public abstract interface LongToDoubleFunction {
+    method public abstract double applyAsDouble(long);
+  }
+
+  public abstract interface LongToIntFunction {
+    method public abstract int applyAsInt(long);
+  }
+
+  public abstract interface LongUnaryOperator {
+    method public default java.util.function.LongUnaryOperator andThen(java.util.function.LongUnaryOperator);
+    method public abstract long applyAsLong(long);
+    method public default java.util.function.LongUnaryOperator compose(java.util.function.LongUnaryOperator);
+    method public static java.util.function.LongUnaryOperator identity();
+  }
+
+  public abstract interface ObjDoubleConsumer {
+    method public abstract void accept(T, double);
+  }
+
+  public abstract interface ObjIntConsumer {
+    method public abstract void accept(T, int);
+  }
+
+  public abstract interface ObjLongConsumer {
+    method public abstract void accept(T, long);
+  }
+
+  public abstract interface Predicate {
+    method public default java.util.function.Predicate<T> and(java.util.function.Predicate<? super T>);
+    method public static java.util.function.Predicate<T> isEqual(java.lang.Object);
+    method public default java.util.function.Predicate<T> negate();
+    method public default java.util.function.Predicate<T> or(java.util.function.Predicate<? super T>);
+    method public abstract boolean test(T);
+  }
+
+  public abstract interface Supplier {
+    method public abstract T get();
+  }
+
+  public abstract interface ToDoubleBiFunction {
+    method public abstract double applyAsDouble(T, U);
+  }
+
+  public abstract interface ToDoubleFunction {
+    method public abstract double applyAsDouble(T);
+  }
+
+  public abstract interface ToIntBiFunction {
+    method public abstract int applyAsInt(T, U);
+  }
+
+  public abstract interface ToIntFunction {
+    method public abstract int applyAsInt(T);
+  }
+
+  public abstract interface ToLongBiFunction {
+    method public abstract long applyAsLong(T, U);
+  }
+
+  public abstract interface ToLongFunction {
+    method public abstract long applyAsLong(T);
+  }
+
+  public abstract interface UnaryOperator implements java.util.function.Function {
+    method public static java.util.function.UnaryOperator<T> identity();
+  }
+
+}
+
 package java.util.jar {
 
   public class Attributes implements java.lang.Cloneable java.util.Map {
diff --git a/api/system-current.txt b/api/system-current.txt
index 287df85..b404bb0 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -293,7 +293,6 @@
 
   public static final class R.attr {
     ctor public R.attr();
-    field public static final int abiOverride = 16844054; // 0x1010516
     field public static final int absListViewStyle = 16842858; // 0x101006a
     field public static final int accessibilityEventTypes = 16843648; // 0x1010380
     field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -1464,6 +1463,7 @@
     field public static final deprecated int unfocusedMonthDateColor = 16843588; // 0x1010344
     field public static final int unselectedAlpha = 16843278; // 0x101020e
     field public static final int updatePeriodMillis = 16843344; // 0x1010250
+    field public static final int use32bitAbi = 16844054; // 0x1010516
     field public static final int useDefaultMargins = 16843641; // 0x1010379
     field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
     field public static final int useLevel = 16843167; // 0x101019f
@@ -24750,7 +24750,7 @@
     method public abstract void onConnect(android.net.Uri);
     method public void onConnect(android.net.Uri, android.os.Bundle);
     method public abstract void onDisconnect();
-    method public abstract void onStartRecording();
+    method public abstract void onStartRecording(android.net.Uri);
     method public abstract void onStopRecording();
   }
 
@@ -24802,7 +24802,7 @@
     method public void connect(java.lang.String, android.net.Uri, android.os.Bundle);
     method public void disconnect();
     method public void sendAppPrivateCommand(java.lang.String, android.os.Bundle);
-    method public void startRecording();
+    method public void startRecording(android.net.Uri);
     method public void stopRecording();
   }
 
@@ -36528,6 +36528,7 @@
 
   public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
     method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+    method public byte[] getAttestationChallenge();
     method public java.lang.String[] getBlockModes();
     method public java.util.Date getCertificateNotAfter();
     method public java.util.Date getCertificateNotBefore();
@@ -36552,6 +36553,7 @@
     ctor public KeyGenParameterSpec.Builder(java.lang.String, int);
     method public android.security.keystore.KeyGenParameterSpec build();
     method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec);
+    method public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
     method public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
     method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(java.util.Date);
     method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotBefore(java.util.Date);
@@ -43413,6 +43415,21 @@
     method public static android.view.FocusFinder getInstance();
   }
 
+  public final class FrameMetrics {
+    ctor public FrameMetrics(android.view.FrameMetrics);
+    method public long getMetric(int);
+    field public static final int ANIMATION_DURATION = 2; // 0x2
+    field public static final int COMMAND_ISSUE_DURATION = 6; // 0x6
+    field public static final int DRAW_DURATION = 4; // 0x4
+    field public static final int FIRST_DRAW_FRAME = 9; // 0x9
+    field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
+    field public static final int LAYOUT_MEASURE_DURATION = 3; // 0x3
+    field public static final int SWAP_BUFFERS_DURATION = 7; // 0x7
+    field public static final int SYNC_DURATION = 5; // 0x5
+    field public static final int TOTAL_DURATION = 8; // 0x8
+    field public static final int UNKNOWN_DELAY_DURATION = 0; // 0x0
+  }
+
   public abstract class FrameStats {
     ctor public FrameStats();
     method public final long getEndTimeNano();
@@ -45916,6 +45933,7 @@
     ctor public Window(android.content.Context);
     method public abstract void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
     method public void addFlags(int);
+    method public final void addFrameMetricsListener(android.view.Window.FrameMetricsListener, android.os.Handler);
     method public void clearFlags(int);
     method public abstract void closeAllPanels();
     method public abstract void closePanel(int);
@@ -45967,6 +45985,7 @@
     method public abstract boolean performContextMenuIdentifierAction(int, int);
     method public abstract boolean performPanelIdentifierAction(int, int, int);
     method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int);
+    method public final void removeFrameMetricsListener(android.view.Window.FrameMetricsListener);
     method public boolean requestFeature(int);
     method public abstract void restoreHierarchyState(android.os.Bundle);
     method public abstract android.os.Bundle saveHierarchyState();
@@ -46093,6 +46112,10 @@
     method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
   }
 
+  public static abstract interface Window.FrameMetricsListener {
+    method public abstract void onMetricsAvailable(android.view.Window, android.view.FrameMetrics, int);
+  }
+
   public static abstract interface Window.OnRestrictedCaptionAreaChangedListener {
     method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
   }
@@ -61104,8 +61127,11 @@
     method public static boolean equals(java.lang.Object, java.lang.Object);
     method public static int hash(java.lang.Object...);
     method public static int hashCode(java.lang.Object);
+    method public static boolean isNull(java.lang.Object);
+    method public static boolean nonNull(java.lang.Object);
     method public static T requireNonNull(T);
     method public static T requireNonNull(T, java.lang.String);
+    method public static T requireNonNull(T, java.util.function.Supplier<java.lang.String>);
     method public static java.lang.String toString(java.lang.Object);
     method public static java.lang.String toString(java.lang.Object, java.lang.String);
   }
@@ -62810,6 +62836,217 @@
 
 }
 
+package java.util.function {
+
+  public abstract interface BiConsumer {
+    method public abstract void accept(T, U);
+    method public default java.util.function.BiConsumer<T, U> andThen(java.util.function.BiConsumer<? super T, ? super U>);
+  }
+
+  public abstract interface BiFunction {
+    method public default java.util.function.BiFunction<T, U, V> andThen(java.util.function.Function<? super R, ? extends V>);
+    method public abstract R apply(T, U);
+  }
+
+  public abstract interface BiPredicate {
+    method public default java.util.function.BiPredicate<T, U> and(java.util.function.BiPredicate<? super T, ? super U>);
+    method public default java.util.function.BiPredicate<T, U> negate();
+    method public default java.util.function.BiPredicate<T, U> or(java.util.function.BiPredicate<? super T, ? super U>);
+    method public abstract boolean test(T, U);
+  }
+
+  public abstract interface BinaryOperator implements java.util.function.BiFunction {
+    method public static java.util.function.BinaryOperator<T> maxBy(java.util.Comparator<? super T>);
+    method public static java.util.function.BinaryOperator<T> minBy(java.util.Comparator<? super T>);
+  }
+
+  public abstract interface BooleanSupplier {
+    method public abstract boolean getAsBoolean();
+  }
+
+  public abstract interface Consumer {
+    method public abstract void accept(T);
+    method public default java.util.function.Consumer<T> andThen(java.util.function.Consumer<? super T>);
+  }
+
+  public abstract interface DoubleBinaryOperator {
+    method public abstract double applyAsDouble(double, double);
+  }
+
+  public abstract interface DoubleConsumer {
+    method public abstract void accept(double);
+    method public default java.util.function.DoubleConsumer andThen(java.util.function.DoubleConsumer);
+  }
+
+  public abstract interface DoubleFunction {
+    method public abstract R apply(double);
+  }
+
+  public abstract interface DoublePredicate {
+    method public default java.util.function.DoublePredicate and(java.util.function.DoublePredicate);
+    method public default java.util.function.DoublePredicate negate();
+    method public default java.util.function.DoublePredicate or(java.util.function.DoublePredicate);
+    method public abstract boolean test(double);
+  }
+
+  public abstract interface DoubleSupplier {
+    method public abstract double getAsDouble();
+  }
+
+  public abstract interface DoubleToIntFunction {
+    method public abstract int applyAsInt(double);
+  }
+
+  public abstract interface DoubleToLongFunction {
+    method public abstract long applyAsLong(double);
+  }
+
+  public abstract interface DoubleUnaryOperator {
+    method public default java.util.function.DoubleUnaryOperator andThen(java.util.function.DoubleUnaryOperator);
+    method public abstract double applyAsDouble(double);
+    method public default java.util.function.DoubleUnaryOperator compose(java.util.function.DoubleUnaryOperator);
+    method public static java.util.function.DoubleUnaryOperator identity();
+  }
+
+  public abstract interface Function {
+    method public default java.util.function.Function<T, V> andThen(java.util.function.Function<? super R, ? extends V>);
+    method public abstract R apply(T);
+    method public default java.util.function.Function<V, R> compose(java.util.function.Function<? super V, ? extends T>);
+    method public static java.util.function.Function<T, T> identity();
+  }
+
+  public abstract interface IntBinaryOperator {
+    method public abstract int applyAsInt(int, int);
+  }
+
+  public abstract interface IntConsumer {
+    method public abstract void accept(int);
+    method public default java.util.function.IntConsumer andThen(java.util.function.IntConsumer);
+  }
+
+  public abstract interface IntFunction {
+    method public abstract R apply(int);
+  }
+
+  public abstract interface IntPredicate {
+    method public default java.util.function.IntPredicate and(java.util.function.IntPredicate);
+    method public default java.util.function.IntPredicate negate();
+    method public default java.util.function.IntPredicate or(java.util.function.IntPredicate);
+    method public abstract boolean test(int);
+  }
+
+  public abstract interface IntSupplier {
+    method public abstract int getAsInt();
+  }
+
+  public abstract interface IntToDoubleFunction {
+    method public abstract double applyAsDouble(int);
+  }
+
+  public abstract interface IntToLongFunction {
+    method public abstract long applyAsLong(int);
+  }
+
+  public abstract interface IntUnaryOperator {
+    method public default java.util.function.IntUnaryOperator andThen(java.util.function.IntUnaryOperator);
+    method public abstract int applyAsInt(int);
+    method public default java.util.function.IntUnaryOperator compose(java.util.function.IntUnaryOperator);
+    method public static java.util.function.IntUnaryOperator identity();
+  }
+
+  public abstract interface LongBinaryOperator {
+    method public abstract long applyAsLong(long, long);
+  }
+
+  public abstract interface LongConsumer {
+    method public abstract void accept(long);
+    method public default java.util.function.LongConsumer andThen(java.util.function.LongConsumer);
+  }
+
+  public abstract interface LongFunction {
+    method public abstract R apply(long);
+  }
+
+  public abstract interface LongPredicate {
+    method public default java.util.function.LongPredicate and(java.util.function.LongPredicate);
+    method public default java.util.function.LongPredicate negate();
+    method public default java.util.function.LongPredicate or(java.util.function.LongPredicate);
+    method public abstract boolean test(long);
+  }
+
+  public abstract interface LongSupplier {
+    method public abstract long getAsLong();
+  }
+
+  public abstract interface LongToDoubleFunction {
+    method public abstract double applyAsDouble(long);
+  }
+
+  public abstract interface LongToIntFunction {
+    method public abstract int applyAsInt(long);
+  }
+
+  public abstract interface LongUnaryOperator {
+    method public default java.util.function.LongUnaryOperator andThen(java.util.function.LongUnaryOperator);
+    method public abstract long applyAsLong(long);
+    method public default java.util.function.LongUnaryOperator compose(java.util.function.LongUnaryOperator);
+    method public static java.util.function.LongUnaryOperator identity();
+  }
+
+  public abstract interface ObjDoubleConsumer {
+    method public abstract void accept(T, double);
+  }
+
+  public abstract interface ObjIntConsumer {
+    method public abstract void accept(T, int);
+  }
+
+  public abstract interface ObjLongConsumer {
+    method public abstract void accept(T, long);
+  }
+
+  public abstract interface Predicate {
+    method public default java.util.function.Predicate<T> and(java.util.function.Predicate<? super T>);
+    method public static java.util.function.Predicate<T> isEqual(java.lang.Object);
+    method public default java.util.function.Predicate<T> negate();
+    method public default java.util.function.Predicate<T> or(java.util.function.Predicate<? super T>);
+    method public abstract boolean test(T);
+  }
+
+  public abstract interface Supplier {
+    method public abstract T get();
+  }
+
+  public abstract interface ToDoubleBiFunction {
+    method public abstract double applyAsDouble(T, U);
+  }
+
+  public abstract interface ToDoubleFunction {
+    method public abstract double applyAsDouble(T);
+  }
+
+  public abstract interface ToIntBiFunction {
+    method public abstract int applyAsInt(T, U);
+  }
+
+  public abstract interface ToIntFunction {
+    method public abstract int applyAsInt(T);
+  }
+
+  public abstract interface ToLongBiFunction {
+    method public abstract long applyAsLong(T, U);
+  }
+
+  public abstract interface ToLongFunction {
+    method public abstract long applyAsLong(T);
+  }
+
+  public abstract interface UnaryOperator implements java.util.function.Function {
+    method public static java.util.function.UnaryOperator<T> identity();
+  }
+
+}
+
 package java.util.jar {
 
   public class Attributes implements java.lang.Cloneable java.util.Map {
diff --git a/api/test-current.txt b/api/test-current.txt
index e7f4336..9f8b8a8 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -198,7 +198,6 @@
 
   public static final class R.attr {
     ctor public R.attr();
-    field public static final int abiOverride = 16844054; // 0x1010516
     field public static final int absListViewStyle = 16842858; // 0x101006a
     field public static final int accessibilityEventTypes = 16843648; // 0x1010380
     field public static final int accessibilityFeedbackType = 16843650; // 0x1010382
@@ -1365,6 +1364,7 @@
     field public static final deprecated int unfocusedMonthDateColor = 16843588; // 0x1010344
     field public static final int unselectedAlpha = 16843278; // 0x101020e
     field public static final int updatePeriodMillis = 16843344; // 0x1010250
+    field public static final int use32bitAbi = 16844054; // 0x1010516
     field public static final int useDefaultMargins = 16843641; // 0x1010379
     field public static final int useIntrinsicSizeAsMinimum = 16843536; // 0x1010310
     field public static final int useLevel = 16843167; // 0x101019f
@@ -23007,7 +23007,7 @@
     method public void notifyRecordingStopped(android.net.Uri);
     method public abstract void onConnect(android.net.Uri);
     method public abstract void onDisconnect();
-    method public abstract void onStartRecording();
+    method public abstract void onStartRecording(android.net.Uri);
     method public abstract void onStopRecording();
   }
 
@@ -23053,7 +23053,7 @@
     ctor public TvRecordingClient(android.content.Context, java.lang.String, android.media.tv.TvRecordingClient.RecordingCallback, android.os.Handler);
     method public void connect(java.lang.String, android.net.Uri);
     method public void disconnect();
-    method public void startRecording();
+    method public void startRecording(android.net.Uri);
     method public void stopRecording();
   }
 
@@ -34060,6 +34060,7 @@
 
   public final class KeyGenParameterSpec implements java.security.spec.AlgorithmParameterSpec {
     method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
+    method public byte[] getAttestationChallenge();
     method public java.lang.String[] getBlockModes();
     method public java.util.Date getCertificateNotAfter();
     method public java.util.Date getCertificateNotBefore();
@@ -34084,6 +34085,7 @@
     ctor public KeyGenParameterSpec.Builder(java.lang.String, int);
     method public android.security.keystore.KeyGenParameterSpec build();
     method public android.security.keystore.KeyGenParameterSpec.Builder setAlgorithmParameterSpec(java.security.spec.AlgorithmParameterSpec);
+    method public android.security.keystore.KeyGenParameterSpec.Builder setAttestationChallenge(byte[]);
     method public android.security.keystore.KeyGenParameterSpec.Builder setBlockModes(java.lang.String...);
     method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotAfter(java.util.Date);
     method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateNotBefore(java.util.Date);
@@ -40678,6 +40680,21 @@
     method public static android.view.FocusFinder getInstance();
   }
 
+  public final class FrameMetrics {
+    ctor public FrameMetrics(android.view.FrameMetrics);
+    method public long getMetric(int);
+    field public static final int ANIMATION_DURATION = 2; // 0x2
+    field public static final int COMMAND_ISSUE_DURATION = 6; // 0x6
+    field public static final int DRAW_DURATION = 4; // 0x4
+    field public static final int FIRST_DRAW_FRAME = 9; // 0x9
+    field public static final int INPUT_HANDLING_DURATION = 1; // 0x1
+    field public static final int LAYOUT_MEASURE_DURATION = 3; // 0x3
+    field public static final int SWAP_BUFFERS_DURATION = 7; // 0x7
+    field public static final int SYNC_DURATION = 5; // 0x5
+    field public static final int TOTAL_DURATION = 8; // 0x8
+    field public static final int UNKNOWN_DELAY_DURATION = 0; // 0x0
+  }
+
   public abstract class FrameStats {
     ctor public FrameStats();
     method public final long getEndTimeNano();
@@ -43181,6 +43198,7 @@
     ctor public Window(android.content.Context);
     method public abstract void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
     method public void addFlags(int);
+    method public final void addFrameMetricsListener(android.view.Window.FrameMetricsListener, android.os.Handler);
     method public void clearFlags(int);
     method public abstract void closeAllPanels();
     method public abstract void closePanel(int);
@@ -43232,6 +43250,7 @@
     method public abstract boolean performContextMenuIdentifierAction(int, int);
     method public abstract boolean performPanelIdentifierAction(int, int, int);
     method public abstract boolean performPanelShortcut(int, int, android.view.KeyEvent, int);
+    method public final void removeFrameMetricsListener(android.view.Window.FrameMetricsListener);
     method public boolean requestFeature(int);
     method public abstract void restoreHierarchyState(android.os.Bundle);
     method public abstract android.os.Bundle saveHierarchyState();
@@ -43357,6 +43376,10 @@
     method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
   }
 
+  public static abstract interface Window.FrameMetricsListener {
+    method public abstract void onMetricsAvailable(android.view.Window, android.view.FrameMetrics, int);
+  }
+
   public static abstract interface Window.OnRestrictedCaptionAreaChangedListener {
     method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
   }
@@ -58033,8 +58056,11 @@
     method public static boolean equals(java.lang.Object, java.lang.Object);
     method public static int hash(java.lang.Object...);
     method public static int hashCode(java.lang.Object);
+    method public static boolean isNull(java.lang.Object);
+    method public static boolean nonNull(java.lang.Object);
     method public static T requireNonNull(T);
     method public static T requireNonNull(T, java.lang.String);
+    method public static T requireNonNull(T, java.util.function.Supplier<java.lang.String>);
     method public static java.lang.String toString(java.lang.Object);
     method public static java.lang.String toString(java.lang.Object, java.lang.String);
   }
@@ -59739,6 +59765,217 @@
 
 }
 
+package java.util.function {
+
+  public abstract interface BiConsumer {
+    method public abstract void accept(T, U);
+    method public default java.util.function.BiConsumer<T, U> andThen(java.util.function.BiConsumer<? super T, ? super U>);
+  }
+
+  public abstract interface BiFunction {
+    method public default java.util.function.BiFunction<T, U, V> andThen(java.util.function.Function<? super R, ? extends V>);
+    method public abstract R apply(T, U);
+  }
+
+  public abstract interface BiPredicate {
+    method public default java.util.function.BiPredicate<T, U> and(java.util.function.BiPredicate<? super T, ? super U>);
+    method public default java.util.function.BiPredicate<T, U> negate();
+    method public default java.util.function.BiPredicate<T, U> or(java.util.function.BiPredicate<? super T, ? super U>);
+    method public abstract boolean test(T, U);
+  }
+
+  public abstract interface BinaryOperator implements java.util.function.BiFunction {
+    method public static java.util.function.BinaryOperator<T> maxBy(java.util.Comparator<? super T>);
+    method public static java.util.function.BinaryOperator<T> minBy(java.util.Comparator<? super T>);
+  }
+
+  public abstract interface BooleanSupplier {
+    method public abstract boolean getAsBoolean();
+  }
+
+  public abstract interface Consumer {
+    method public abstract void accept(T);
+    method public default java.util.function.Consumer<T> andThen(java.util.function.Consumer<? super T>);
+  }
+
+  public abstract interface DoubleBinaryOperator {
+    method public abstract double applyAsDouble(double, double);
+  }
+
+  public abstract interface DoubleConsumer {
+    method public abstract void accept(double);
+    method public default java.util.function.DoubleConsumer andThen(java.util.function.DoubleConsumer);
+  }
+
+  public abstract interface DoubleFunction {
+    method public abstract R apply(double);
+  }
+
+  public abstract interface DoublePredicate {
+    method public default java.util.function.DoublePredicate and(java.util.function.DoublePredicate);
+    method public default java.util.function.DoublePredicate negate();
+    method public default java.util.function.DoublePredicate or(java.util.function.DoublePredicate);
+    method public abstract boolean test(double);
+  }
+
+  public abstract interface DoubleSupplier {
+    method public abstract double getAsDouble();
+  }
+
+  public abstract interface DoubleToIntFunction {
+    method public abstract int applyAsInt(double);
+  }
+
+  public abstract interface DoubleToLongFunction {
+    method public abstract long applyAsLong(double);
+  }
+
+  public abstract interface DoubleUnaryOperator {
+    method public default java.util.function.DoubleUnaryOperator andThen(java.util.function.DoubleUnaryOperator);
+    method public abstract double applyAsDouble(double);
+    method public default java.util.function.DoubleUnaryOperator compose(java.util.function.DoubleUnaryOperator);
+    method public static java.util.function.DoubleUnaryOperator identity();
+  }
+
+  public abstract interface Function {
+    method public default java.util.function.Function<T, V> andThen(java.util.function.Function<? super R, ? extends V>);
+    method public abstract R apply(T);
+    method public default java.util.function.Function<V, R> compose(java.util.function.Function<? super V, ? extends T>);
+    method public static java.util.function.Function<T, T> identity();
+  }
+
+  public abstract interface IntBinaryOperator {
+    method public abstract int applyAsInt(int, int);
+  }
+
+  public abstract interface IntConsumer {
+    method public abstract void accept(int);
+    method public default java.util.function.IntConsumer andThen(java.util.function.IntConsumer);
+  }
+
+  public abstract interface IntFunction {
+    method public abstract R apply(int);
+  }
+
+  public abstract interface IntPredicate {
+    method public default java.util.function.IntPredicate and(java.util.function.IntPredicate);
+    method public default java.util.function.IntPredicate negate();
+    method public default java.util.function.IntPredicate or(java.util.function.IntPredicate);
+    method public abstract boolean test(int);
+  }
+
+  public abstract interface IntSupplier {
+    method public abstract int getAsInt();
+  }
+
+  public abstract interface IntToDoubleFunction {
+    method public abstract double applyAsDouble(int);
+  }
+
+  public abstract interface IntToLongFunction {
+    method public abstract long applyAsLong(int);
+  }
+
+  public abstract interface IntUnaryOperator {
+    method public default java.util.function.IntUnaryOperator andThen(java.util.function.IntUnaryOperator);
+    method public abstract int applyAsInt(int);
+    method public default java.util.function.IntUnaryOperator compose(java.util.function.IntUnaryOperator);
+    method public static java.util.function.IntUnaryOperator identity();
+  }
+
+  public abstract interface LongBinaryOperator {
+    method public abstract long applyAsLong(long, long);
+  }
+
+  public abstract interface LongConsumer {
+    method public abstract void accept(long);
+    method public default java.util.function.LongConsumer andThen(java.util.function.LongConsumer);
+  }
+
+  public abstract interface LongFunction {
+    method public abstract R apply(long);
+  }
+
+  public abstract interface LongPredicate {
+    method public default java.util.function.LongPredicate and(java.util.function.LongPredicate);
+    method public default java.util.function.LongPredicate negate();
+    method public default java.util.function.LongPredicate or(java.util.function.LongPredicate);
+    method public abstract boolean test(long);
+  }
+
+  public abstract interface LongSupplier {
+    method public abstract long getAsLong();
+  }
+
+  public abstract interface LongToDoubleFunction {
+    method public abstract double applyAsDouble(long);
+  }
+
+  public abstract interface LongToIntFunction {
+    method public abstract int applyAsInt(long);
+  }
+
+  public abstract interface LongUnaryOperator {
+    method public default java.util.function.LongUnaryOperator andThen(java.util.function.LongUnaryOperator);
+    method public abstract long applyAsLong(long);
+    method public default java.util.function.LongUnaryOperator compose(java.util.function.LongUnaryOperator);
+    method public static java.util.function.LongUnaryOperator identity();
+  }
+
+  public abstract interface ObjDoubleConsumer {
+    method public abstract void accept(T, double);
+  }
+
+  public abstract interface ObjIntConsumer {
+    method public abstract void accept(T, int);
+  }
+
+  public abstract interface ObjLongConsumer {
+    method public abstract void accept(T, long);
+  }
+
+  public abstract interface Predicate {
+    method public default java.util.function.Predicate<T> and(java.util.function.Predicate<? super T>);
+    method public static java.util.function.Predicate<T> isEqual(java.lang.Object);
+    method public default java.util.function.Predicate<T> negate();
+    method public default java.util.function.Predicate<T> or(java.util.function.Predicate<? super T>);
+    method public abstract boolean test(T);
+  }
+
+  public abstract interface Supplier {
+    method public abstract T get();
+  }
+
+  public abstract interface ToDoubleBiFunction {
+    method public abstract double applyAsDouble(T, U);
+  }
+
+  public abstract interface ToDoubleFunction {
+    method public abstract double applyAsDouble(T);
+  }
+
+  public abstract interface ToIntBiFunction {
+    method public abstract int applyAsInt(T, U);
+  }
+
+  public abstract interface ToIntFunction {
+    method public abstract int applyAsInt(T);
+  }
+
+  public abstract interface ToLongBiFunction {
+    method public abstract long applyAsLong(T, U);
+  }
+
+  public abstract interface ToLongFunction {
+    method public abstract long applyAsLong(T);
+  }
+
+  public abstract interface UnaryOperator implements java.util.function.Function {
+    method public static java.util.function.UnaryOperator<T> identity();
+  }
+
+}
+
 package java.util.jar {
 
   public class Attributes implements java.lang.Cloneable java.util.Map {
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 1954774..99852b8 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -2882,6 +2882,11 @@
             reply.writeInt(isForeground ? 1 : 0);
             return true;
         }
+        case NOTIFY_PINNED_STACK_ANIMATION_ENDED_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -6732,5 +6737,15 @@
         return isForeground;
     };
 
+    @Override
+    public void notifyPinnedStackAnimationEnded() throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        mRemote.transact(NOTIFY_PINNED_STACK_ANIMATION_ENDED_TRANSACTION, data, reply, 0);
+        data.recycle();
+        reply.recycle();
+    };
+
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index b5ca6ee..98ce273 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -597,6 +597,8 @@
 
     public boolean supportsLocalVoiceInteraction() throws RemoteException;
 
+    public void notifyPinnedStackAnimationEnded() throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -972,4 +974,5 @@
     int START_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 363;
     int STOP_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 364;
     int SUPPORTS_LOCAL_VOICE_INTERACTION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 365;
+    int NOTIFY_PINNED_STACK_ANIMATION_ENDED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 366;
 }
diff --git a/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index fa11234..6432558 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -30,4 +30,9 @@
      * brought to the front or a new Intent is delivered to it.
      */
     void onPinnedActivityRestartAttempt();
+
+    /**
+     * Called whenever the pinned stack is done animating a resize.
+     */
+    void onPinnedStackAnimationEnded();
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 9a88f2c..33fd1db 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1842,13 +1842,10 @@
      * @see UiAutomation
      */
     public UiAutomation getUiAutomation() {
-        if (mUiAutomationConnection != null) {
-            if (mUiAutomation == null) {
-                return getUiAutomation(0);
-            }
-            return mUiAutomation;
+        if ((mUiAutomation == null) || (mUiAutomation.isDestroyed())) {
+            return getUiAutomation(0);
         }
-        return null;
+        return mUiAutomation;
     }
 
     /**
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index da52c1e..8717353 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -447,7 +447,8 @@
         IPackageManager pm = ActivityThread.getPackageManager();
         android.content.pm.PackageInfo pi;
         try {
-            pi = pm.getPackageInfo(mPackageName, 0, UserHandle.myUserId());
+            pi = pm.getPackageInfo(mPackageName, PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+                    UserHandle.myUserId());
         } catch (RemoteException e) {
             throw new IllegalStateException("Unable to get package info for "
                     + mPackageName + "; is system dying?", e);
diff --git a/core/java/android/app/SearchableInfo.java b/core/java/android/app/SearchableInfo.java
index c7d2140..a952915 100644
--- a/core/java/android/app/SearchableInfo.java
+++ b/core/java/android/app/SearchableInfo.java
@@ -363,7 +363,8 @@
         String suggestProviderPackage = null;
         if (mSuggestAuthority != null) {
             PackageManager pm = activityContext.getPackageManager();
-            ProviderInfo pi = pm.resolveContentProvider(mSuggestAuthority, 0);
+            ProviderInfo pi = pm.resolveContentProvider(mSuggestAuthority,
+                    PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
             if (pi != null) {
                 suggestProviderPackage = pi.packageName;
             }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 72ace15..5dddebd 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -337,7 +337,7 @@
 
         public final boolean coreApp;
         public final boolean multiArch;
-        public final String abiOverride;
+        public final boolean use32bitAbi;
         public final boolean extractNativeLibs;
 
         public PackageLite(String codePath, ApkLite baseApk, String[] splitNames,
@@ -354,7 +354,7 @@
             this.splitRevisionCodes = splitRevisionCodes;
             this.coreApp = baseApk.coreApp;
             this.multiArch = baseApk.multiArch;
-            this.abiOverride = baseApk.abiOverride;
+            this.use32bitAbi = baseApk.use32bitAbi;
             this.extractNativeLibs = baseApk.extractNativeLibs;
         }
 
@@ -382,12 +382,12 @@
         public final Signature[] signatures;
         public final boolean coreApp;
         public final boolean multiArch;
-        public final String abiOverride;
+        public final boolean use32bitAbi;
         public final boolean extractNativeLibs;
 
         public ApkLite(String codePath, String packageName, String splitName, int versionCode,
                 int revisionCode, int installLocation, List<VerifierInfo> verifiers,
-                Signature[] signatures, boolean coreApp, boolean multiArch, String abiOverride,
+                Signature[] signatures, boolean coreApp, boolean multiArch, boolean use32bitAbi,
                 boolean extractNativeLibs) {
             this.codePath = codePath;
             this.packageName = packageName;
@@ -399,7 +399,7 @@
             this.signatures = signatures;
             this.coreApp = coreApp;
             this.multiArch = multiArch;
-            this.abiOverride = abiOverride;
+            this.use32bitAbi = use32bitAbi;
             this.extractNativeLibs = extractNativeLibs;
         }
     }
@@ -843,8 +843,7 @@
             }
 
             pkg.setCodePath(packageDir.getAbsolutePath());
-            pkg.setCpuAbiOverride(lite.abiOverride);
-
+            pkg.setUse32bitAbi(lite.use32bitAbi);
             return pkg;
         } finally {
             IoUtils.closeQuietly(assets);
@@ -875,7 +874,7 @@
         try {
             final Package pkg = parseBaseApk(apkFile, assets, flags);
             pkg.setCodePath(apkFile.getAbsolutePath());
-            pkg.setCpuAbiOverride(lite.abiOverride);
+            pkg.setUse32bitAbi(lite.use32bitAbi);
             return pkg;
         } finally {
             IoUtils.closeQuietly(assets);
@@ -1380,7 +1379,7 @@
         int revisionCode = 0;
         boolean coreApp = false;
         boolean multiArch = false;
-        String abiOverride = null;
+        boolean use32bitAbi = false;
         boolean extractNativeLibs = true;
 
         for (int i = 0; i < attrs.getAttributeCount(); i++) {
@@ -1421,8 +1420,8 @@
                     if ("multiArch".equals(attr)) {
                         multiArch = attrs.getAttributeBooleanValue(i, false);
                     }
-                    if ("abiOverride".equals(attr)) {
-                        abiOverride = attrs.getAttributeValue(i);
+                    if ("use32bitAbi".equals(attr)) {
+                        use32bitAbi = attrs.getAttributeBooleanValue(i, false);
                     }
                     if ("extractNativeLibs".equals(attr)) {
                         extractNativeLibs = attrs.getAttributeBooleanValue(i, true);
@@ -1433,7 +1432,7 @@
 
         return new ApkLite(codePath, packageSplit.first, packageSplit.second, versionCode,
                 revisionCode, installLocation, verifiers, signatures, coreApp, multiArch,
-                abiOverride, extractNativeLibs);
+                use32bitAbi, extractNativeLibs);
     }
 
     /**
@@ -4740,6 +4739,12 @@
          * and prods fields out of {@code this.applicationInfo}.
          */
         public String cpuAbiOverride;
+        /**
+         * The install time abi override to choose 32bit abi's when multiple abi's
+         * are present. This is only meaningfull for multiarch applications.
+         * The use32bitAbi attribute is ignored if cpuAbiOverride is also set.
+         */
+        public boolean use32bitAbi;
 
         public Package(String packageName) {
             this.packageName = packageName;
@@ -4872,12 +4877,12 @@
             }
         }
 
-        public void setCpuAbiOverride(String cpuAbiOverride) {
-            this.cpuAbiOverride = cpuAbiOverride;
+        public void setUse32bitAbi(boolean use32bitAbi) {
+            this.use32bitAbi = use32bitAbi;
             if (childPackages != null) {
                 final int packageCount = childPackages.size();
                 for (int i = 0; i < packageCount; i++) {
-                    childPackages.get(i).cpuAbiOverride = cpuAbiOverride;
+                    childPackages.get(i).use32bitAbi = use32bitAbi;
                 }
             }
         }
diff --git a/core/java/android/security/IKeystoreService.aidl b/core/java/android/security/IKeystoreService.aidl
index 7cf1d71..8689dce 100644
--- a/core/java/android/security/IKeystoreService.aidl
+++ b/core/java/android/security/IKeystoreService.aidl
@@ -19,6 +19,7 @@
 import android.security.keymaster.ExportResult;
 import android.security.keymaster.KeyCharacteristics;
 import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keymaster.KeymasterBlob;
 import android.security.keymaster.OperationResult;
 import android.security.KeystoreArguments;
@@ -74,4 +75,5 @@
     int addAuthToken(in byte[] authToken);
     int onUserAdded(int userId, int parentId);
     int onUserRemoved(int userId);
+    int attestKey(String alias, in KeymasterArguments params, out KeymasterCertificateChain chain);
 }
diff --git a/libs/hwui/FrameStatsObserver.h b/core/java/android/security/keymaster/KeymasterCertificateChain.aidl
similarity index 68%
copy from libs/hwui/FrameStatsObserver.h
copy to core/java/android/security/keymaster/KeymasterCertificateChain.aidl
index 7abc9f1..dc1876a 100644
--- a/libs/hwui/FrameStatsObserver.h
+++ b/core/java/android/security/keymaster/KeymasterCertificateChain.aidl
@@ -14,19 +14,7 @@
  * limitations under the License.
  */
 
-#pragma once
+package android.security.keymaster;
 
-#include <utils/RefBase.h>
-
-#include "BufferPool.h"
-
-namespace android {
-namespace uirenderer {
-
-class FrameStatsObserver : public VirtualLightRefBase {
-public:
-    virtual void notify(BufferPool::Buffer* buffer);
-};
-
-}; // namespace uirenderer
-}; // namespace android
+/* @hide */
+parcelable KeymasterCertificateChain;
diff --git a/core/java/android/security/keymaster/KeymasterCertificateChain.java b/core/java/android/security/keymaster/KeymasterCertificateChain.java
new file mode 100644
index 0000000..243b9fe
--- /dev/null
+++ b/core/java/android/security/keymaster/KeymasterCertificateChain.java
@@ -0,0 +1,85 @@
+/*
+ * 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.security.keymaster;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for the Java side of keystore-generated certificate chains.
+ *
+ * Serialization code for this must be kept in sync with system/security/keystore
+ * @hide
+ */
+public class KeymasterCertificateChain implements Parcelable {
+
+    private List<byte[]> mCertificates;
+
+    public static final Parcelable.Creator<KeymasterCertificateChain> CREATOR = new
+            Parcelable.Creator<KeymasterCertificateChain>() {
+                public KeymasterCertificateChain createFromParcel(Parcel in) {
+                    return new KeymasterCertificateChain(in);
+                }
+                public KeymasterCertificateChain[] newArray(int size) {
+                    return new KeymasterCertificateChain[size];
+                }
+            };
+
+    public KeymasterCertificateChain() {
+        mCertificates = null;
+    }
+
+    public KeymasterCertificateChain(List<byte[]> mCertificates) {
+        this.mCertificates = mCertificates;
+    }
+
+    private KeymasterCertificateChain(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public List<byte[]> getCertificates() {
+        return mCertificates;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        if (mCertificates == null) {
+            out.writeInt(0);
+        } else {
+            out.writeInt(mCertificates.size());
+            for (byte[] arg : mCertificates) {
+                out.writeByteArray(arg);
+            }
+        }
+    }
+
+    public void readFromParcel(Parcel in) {
+        int length = in.readInt();
+        mCertificates = new ArrayList<byte[]>(length);
+        for (int i = 0; i < length; i++) {
+            mCertificates.add(in.createByteArray());
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/security/keymaster/KeymasterDefs.java b/core/java/android/security/keymaster/KeymasterDefs.java
index 04d5952..e01f2a0 100644
--- a/core/java/android/security/keymaster/KeymasterDefs.java
+++ b/core/java/android/security/keymaster/KeymasterDefs.java
@@ -58,6 +58,8 @@
     public static final int KM_TAG_BLOB_USAGE_REQUIREMENTS = KM_ENUM | 705;
 
     public static final int KM_TAG_RSA_PUBLIC_EXPONENT = KM_ULONG | 200;
+    public static final int KM_TAG_INCLUDE_UNIQUE_ID = KM_BOOL | 202;
+
     public static final int KM_TAG_ACTIVE_DATETIME = KM_DATE | 400;
     public static final int KM_TAG_ORIGINATION_EXPIRE_DATETIME = KM_DATE | 401;
     public static final int KM_TAG_USAGE_EXPIRE_DATETIME = KM_DATE | 402;
@@ -74,11 +76,12 @@
     public static final int KM_TAG_ALL_APPLICATIONS = KM_BOOL | 600;
     public static final int KM_TAG_APPLICATION_ID = KM_BYTES | 601;
 
-    public static final int KM_TAG_APPLICATION_DATA = KM_BYTES | 700;
     public static final int KM_TAG_CREATION_DATETIME = KM_DATE | 701;
     public static final int KM_TAG_ORIGIN = KM_ENUM | 702;
     public static final int KM_TAG_ROLLBACK_RESISTANT = KM_BOOL | 703;
     public static final int KM_TAG_ROOT_OF_TRUST = KM_BYTES | 704;
+    public static final int KM_TAG_UNIQUE_ID = KM_BYTES | 707;
+    public static final int KM_TAG_ATTESTATION_CHALLENGE = KM_BYTES | 708;
 
     public static final int KM_TAG_ASSOCIATED_DATA = KM_BYTES | 1000;
     public static final int KM_TAG_NONCE = KM_BYTES | 1001;
diff --git a/core/java/android/view/FrameMetrics.java b/core/java/android/view/FrameMetrics.java
new file mode 100644
index 0000000..8e66f86
--- /dev/null
+++ b/core/java/android/view/FrameMetrics.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IntDef;
+import android.view.Window;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Class containing timing data for various milestones in a frame
+ * lifecycle reported by the rendering subsystem.
+ * <p>
+ * Supported metrics can be queried via their corresponding identifier.
+ * </p>
+ */
+public final class FrameMetrics {
+
+    /**
+     * Metric identifier for unknown delay.
+     * <p>
+     * Represents the number of nanoseconds elapsed waiting for the
+     * UI thread to become responsive and process the frame. This
+     * should be 0 most of the time.
+     * </p>
+     */
+    public static final int UNKNOWN_DELAY_DURATION = 0;
+
+    /**
+     * Metric identifier for input handling duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed issuing
+     * input handling callbacks.
+     * </p>
+     */
+    public static final int INPUT_HANDLING_DURATION = 1;
+
+    /**
+     * Metric identifier for animation callback duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed issuing
+     * animation callbacks.
+     * </p>
+     */
+    public static final int ANIMATION_DURATION = 2;
+
+    /**
+     * Metric identifier for layout/measure duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed measuring
+     * and laying out the invalidated pieces of the view hierarchy.
+     * </p>
+     */
+    public static final int LAYOUT_MEASURE_DURATION = 3;
+    /**
+     * Metric identifier for draw duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed computing
+     * DisplayLists for transformations applied to the view
+     * hierarchy.
+     * </p>
+     */
+    public static final int DRAW_DURATION = 4;
+
+    /**
+     * Metric identifier for sync duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed
+     * synchronizing the computed display lists with the render
+     * thread.
+     * </p>
+     */
+    public static final int SYNC_DURATION = 5;
+
+    /**
+     * Metric identifier for command issue duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed
+     * issuing draw commands to the GPU.
+     * </p>
+     */
+    public static final int COMMAND_ISSUE_DURATION = 6;
+
+    /**
+     * Metric identifier for swap buffers duration.
+     * <p>
+     * Represents the number of nanoseconds elapsed issuing
+     * the frame buffer for this frame to the display
+     * subsystem.
+     * </p>
+     */
+    public static final int SWAP_BUFFERS_DURATION = 7;
+
+    /**
+     * Metric identifier for total frame duration.
+     * <p>
+     * Represents the total time in nanoseconds this frame took to render
+     * and be issued to the display subsystem.
+     * </p>
+     * <p>
+     * Equal to the sum of the values of all other time-valued metric
+     * identifiers.
+     * </p>
+     */
+    public static final int TOTAL_DURATION = 8;
+
+    /**
+     * Metric identifier for a boolean value determining whether this frame was
+     * the first to draw in a new Window layout.
+     * <p>
+     * {@link #getMetric(int)} will return 0 for false, 1 for true.
+     * </p>
+     * <p>
+     * First draw frames are expected to be slow and should usually be exempt
+     * from display jank calculations as they do not cause skips in animations
+     * and are usually hidden by window animations or other tricks.
+     * </p>
+     */
+    public static final int FIRST_DRAW_FRAME = 9;
+
+    private static final int FRAME_INFO_FLAG_FIRST_DRAW = 1 << 0;
+
+    /**
+     * Identifiers for metrics available for each frame.
+     *
+     * {@see {@link #getMetric(int)}}
+     * @hide
+     */
+    @IntDef({
+            UNKNOWN_DELAY_DURATION,
+            INPUT_HANDLING_DURATION,
+            ANIMATION_DURATION,
+            LAYOUT_MEASURE_DURATION,
+            DRAW_DURATION,
+            SYNC_DURATION,
+            COMMAND_ISSUE_DURATION,
+            SWAP_BUFFERS_DURATION,
+            TOTAL_DURATION,
+            FIRST_DRAW_FRAME,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Metric {}
+
+    /**
+     * Timestamp indices for frame milestones.
+     *
+     * May change from release to release.
+     *
+     * Must be kept in sync with frameworks/base/libs/hwui/FrameInfo.h.
+     *
+     * @hide
+     */
+    @IntDef ({
+            Index.FLAGS,
+            Index.INTENDED_VSYNC,
+            Index.VSYNC,
+            Index.OLDEST_INPUT_EVENT,
+            Index.NEWEST_INPUT_EVENT,
+            Index.HANDLE_INPUT_START,
+            Index.ANIMATION_START,
+            Index.PERFORM_TRAVERSALS_START,
+            Index.DRAW_START,
+            Index.SYNC_QUEUED,
+            Index.SYNC_START,
+            Index.ISSUE_DRAW_COMMANDS_START,
+            Index.SWAP_BUFFERS,
+            Index.FRAME_COMPLETED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface Index {
+        int FLAGS = 0;
+        int INTENDED_VSYNC = 1;
+        int VSYNC = 2;
+        int OLDEST_INPUT_EVENT = 3;
+        int NEWEST_INPUT_EVENT = 4;
+        int HANDLE_INPUT_START = 5;
+        int ANIMATION_START = 6;
+        int PERFORM_TRAVERSALS_START = 7;
+        int DRAW_START = 8;
+        int SYNC_QUEUED = 9;
+        int SYNC_START = 10;
+        int ISSUE_DRAW_COMMANDS_START = 11;
+        int SWAP_BUFFERS = 12;
+        int FRAME_COMPLETED = 13;
+
+        int FRAME_STATS_COUNT = 14; // must always be last
+    }
+
+    /*
+     * Bucket endpoints for each Metric defined above.
+     *
+     * Each defined metric *must* have a corresponding entry
+     * in this list.
+     */
+    private static final int[] DURATIONS = new int[] {
+        // UNKNOWN_DELAY
+        Index.INTENDED_VSYNC, Index.HANDLE_INPUT_START,
+        // INPUT_HANDLING
+        Index.HANDLE_INPUT_START, Index.ANIMATION_START,
+        // ANIMATION
+        Index.ANIMATION_START, Index.PERFORM_TRAVERSALS_START,
+        // LAYOUT_MEASURE
+        Index.PERFORM_TRAVERSALS_START, Index.DRAW_START,
+        // DRAW
+        Index.DRAW_START, Index.SYNC_QUEUED,
+        // SYNC
+        Index.SYNC_START, Index.ISSUE_DRAW_COMMANDS_START,
+        // COMMAND_ISSUE
+        Index.ISSUE_DRAW_COMMANDS_START, Index.SWAP_BUFFERS,
+        // SWAP_BUFFERS
+        Index.SWAP_BUFFERS, Index.FRAME_COMPLETED,
+        // TOTAL_DURATION
+        Index.INTENDED_VSYNC, Index.FRAME_COMPLETED,
+    };
+
+    /* package */ final long[] mTimingData;
+
+    /**
+     * Constructs a FrameMetrics object as a copy.
+     * <p>
+     * Use this method to copy out metrics reported by
+     * {@link Window.FrameMetricsListener#onMetricsAvailable(Window, FrameMetrics, int)}
+     * </p>
+     * @param other the FrameMetrics object to copy.
+     */
+    public FrameMetrics(FrameMetrics other) {
+        mTimingData = new long[Index.FRAME_STATS_COUNT];
+        System.arraycopy(other.mTimingData, 0, mTimingData, 0, mTimingData.length);
+    }
+
+    /**
+     * @hide
+     */
+    FrameMetrics() {
+        mTimingData = new long[Index.FRAME_STATS_COUNT];
+    }
+
+    /**
+     * Retrieves the value associated with Metric identifier {@code id}
+     * for this frame.
+     * <p>
+     * Boolean metrics are represented in [0,1], with 0 corresponding to
+     * false, and 1 corresponding to true.
+     * </p>
+     * @param id the metric to retrieve
+     * @return the value of the metric or -1 if it is not available.
+     */
+    public long getMetric(@Metric int id) {
+        if (id < UNKNOWN_DELAY_DURATION || id > FIRST_DRAW_FRAME) {
+            return -1;
+        }
+
+        if (mTimingData == null) {
+            return -1;
+        }
+
+        if (id == FIRST_DRAW_FRAME) {
+            return (mTimingData[Index.FLAGS] & FRAME_INFO_FLAG_FIRST_DRAW) != 0 ? 1 : 0;
+        }
+
+        int durationsIdx = 2 * id;
+        return mTimingData[DURATIONS[durationsIdx + 1]]
+                - mTimingData[DURATIONS[durationsIdx]];
+    }
+}
+
diff --git a/core/java/android/view/FrameMetricsObserver.java b/core/java/android/view/FrameMetricsObserver.java
new file mode 100644
index 0000000..f38f8b7
--- /dev/null
+++ b/core/java/android/view/FrameMetricsObserver.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.NonNull;
+import android.util.Log;
+import android.os.Looper;
+import android.os.MessageQueue;
+
+import com.android.internal.util.VirtualRefBasePtr;
+
+import java.lang.NullPointerException;
+import java.lang.ref.WeakReference;
+import java.lang.SuppressWarnings;
+
+/**
+ * Provides streaming access to frame stats information from the rendering
+ * subsystem to apps.
+ *
+ * @hide
+ */
+public class FrameMetricsObserver {
+    private MessageQueue mMessageQueue;
+
+    private WeakReference<Window> mWindow;
+
+    private FrameMetrics mFrameMetrics;
+
+    /* package */ Window.FrameMetricsListener mListener;
+    /* package */ VirtualRefBasePtr mNative;
+
+    /**
+     * Creates a FrameMetricsObserver
+     *
+     * @param looper the looper to use when invoking callbacks
+     */
+    FrameMetricsObserver(@NonNull Window window, @NonNull Looper looper,
+            @NonNull Window.FrameMetricsListener listener) {
+        if (looper == null) {
+            throw new NullPointerException("looper cannot be null");
+        }
+
+        mMessageQueue = looper.getQueue();
+        if (mMessageQueue == null) {
+            throw new IllegalStateException("invalid looper, null message queue\n");
+        }
+
+        mFrameMetrics = new FrameMetrics();
+        mWindow = new WeakReference<>(window);
+        mListener = listener;
+    }
+
+    // Called by native on the provided Handler
+    @SuppressWarnings("unused")
+    private void notifyDataAvailable(int dropCount) {
+        final Window window = mWindow.get();
+        if (window != null) {
+            mListener.onMetricsAvailable(window, mFrameMetrics, dropCount);
+        }
+    }
+}
diff --git a/core/java/android/view/FrameStatsObserver.java b/core/java/android/view/FrameStatsObserver.java
deleted file mode 100644
index 0add607..0000000
--- a/core/java/android/view/FrameStatsObserver.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view;
-
-import android.annotation.NonNull;
-import android.util.Log;
-import android.os.Looper;
-import android.os.MessageQueue;
-
-import com.android.internal.util.VirtualRefBasePtr;
-
-import java.lang.NullPointerException;
-import java.lang.ref.WeakReference;
-import java.lang.SuppressWarnings;
-
-/**
- * Provides streaming access to frame stats information from the rendering
- * subsystem to apps.
- *
- * @hide
- */
-public abstract class FrameStatsObserver {
-    private static final String TAG = "FrameStatsObserver";
-
-    private MessageQueue mMessageQueue;
-    private long[] mBuffer;
-
-    private FrameStats mFrameStats;
-
-    /* package */ ThreadedRenderer mRenderer;
-    /* package */ VirtualRefBasePtr mNative;
-
-    /**
-     * Containing class for frame statistics reported
-     * by the rendering subsystem.
-     */
-    public static class FrameStats {
-        /**
-         * Precise timing data for various milestones in a frame
-         * lifecycle.
-         *
-         * This data is exactly the same as what is returned by
-         * `adb shell dumpsys gfxinfo <PACKAGE_NAME> framestats`
-         *
-         * The fields reported may change from release to release.
-         *
-         * @see {@link http://developer.android.com/training/testing/performance.html}
-         * for a description of the fields present.
-         */
-        public long[] mTimingData;
-    }
-
-    /**
-     * Creates a FrameStatsObserver
-     *
-     * @param looper the looper to use when invoking callbacks
-     */
-    public FrameStatsObserver(@NonNull Looper looper) {
-        if (looper == null) {
-            throw new NullPointerException("looper cannot be null");
-        }
-
-        mMessageQueue = looper.getQueue();
-        if (mMessageQueue == null) {
-            throw new IllegalStateException("invalid looper, null message queue\n");
-        }
-
-        mFrameStats = new FrameStats();
-    }
-
-    /**
-     * Called on provided looper when frame stats data is available
-     * for the previous frame.
-     *
-     * Clients of this class must do as little work as possible within
-     * this callback, as the buffer is shared between the producer and consumer.
-     *
-     * If the consumer is still executing within this method when there is new
-     * data available that data will be dropped. The producer cannot
-     * wait on the consumer.
-     *
-     * @param data the newly available data
-     */
-    public abstract void onDataAvailable(FrameStats data);
-
-    /**
-     * Returns the number of reports dropped as a result of a slow
-     * consumer.
-     */
-    public long getDroppedReportCount() {
-        if (mRenderer == null) {
-            return 0;
-        }
-
-        return mRenderer.getDroppedFrameReportCount();
-    }
-
-    public boolean isRegistered() {
-        return mRenderer != null && mNative != null;
-    }
-
-    // === called by native === //
-    @SuppressWarnings("unused")
-    private void notifyDataAvailable() {
-        mFrameStats.mTimingData = mBuffer;
-        onDataAvailable(mFrameStats);
-    }
-}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 8b06ecf..ca41d78 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -354,8 +354,6 @@
     private boolean mEnabled;
     private boolean mRequested = true;
 
-    private HashSet<FrameStatsObserver> mFrameStatsObservers;
-
     ThreadedRenderer(Context context, boolean translucent) {
         final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
         mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
@@ -964,29 +962,14 @@
         }
     }
 
-    void addFrameStatsObserver(FrameStatsObserver fso) {
-        if (mFrameStatsObservers == null) {
-            mFrameStatsObservers = new HashSet<>();
-        }
-
-        long nativeFso = nAddFrameStatsObserver(mNativeProxy, fso);
-        fso.mRenderer = this;
-        fso.mNative = new VirtualRefBasePtr(nativeFso);
-        mFrameStatsObservers.add(fso);
+    void addFrameMetricsObserver(FrameMetricsObserver observer) {
+        long nativeObserver = nAddFrameMetricsObserver(mNativeProxy, observer);
+        observer.mNative = new VirtualRefBasePtr(nativeObserver);
     }
 
-    void removeFrameStatsObserver(FrameStatsObserver fso) {
-        if (!mFrameStatsObservers.remove(fso)) {
-            throw new IllegalArgumentException("attempt to remove FrameStatsObserver that was never added");
-        }
-
-        nRemoveFrameStatsObserver(mNativeProxy, fso.mNative.get());
-        fso.mRenderer = null;
-        fso.mNative = null;
-    }
-
-    long getDroppedFrameReportCount() {
-        return nGetDroppedFrameReportCount(mNativeProxy);
+    void removeFrameMetricsObserver(FrameMetricsObserver observer) {
+        nRemoveFrameMetricsObserver(mNativeProxy, observer.mNative.get());
+        observer.mNative = null;
     }
 
     static native void setupShadersDiskCache(String cacheFile);
@@ -1044,7 +1027,6 @@
     private static native void nSetContentDrawBounds(long nativeProxy, int left,
              int top, int right, int bottom);
 
-    private static native long nAddFrameStatsObserver(long nativeProxy, FrameStatsObserver fso);
-    private static native void nRemoveFrameStatsObserver(long nativeProxy, long nativeFso);
-    private static native long nGetDroppedFrameReportCount(long nativeProxy);
+    private static native long nAddFrameMetricsObserver(long nativeProxy, FrameMetricsObserver observer);
+    private static native void nRemoveFrameMetricsObserver(long nativeProxy, long nativeObserver);
 }
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index f52b2907..2612ab2 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -3703,9 +3703,9 @@
     private ViewPropertyAnimator mAnimator = null;
 
     /**
-     * List of FrameStatsObservers pending registration when mAttachInfo is null.
+     * List of registered FrameMetricsObservers.
      */
-    private ArrayList<FrameStatsObserver> mPendingFrameStatsObservers;
+    private ArrayList<FrameMetricsObserver> mFrameMetricsObservers;
 
     /**
      * Flag indicating that a drag can cross window boundaries.  When
@@ -5479,19 +5479,29 @@
      *
      * @hide
      */
-    public void addFrameStatsObserver(FrameStatsObserver fso) {
+    public void addFrameMetricsListener(Window window, Window.FrameMetricsListener listener,
+            Handler handler) {
         if (mAttachInfo != null) {
             if (mAttachInfo.mHardwareRenderer != null) {
-                mAttachInfo.mHardwareRenderer.addFrameStatsObserver(fso);
+                if (mFrameMetricsObservers == null) {
+                    mFrameMetricsObservers = new ArrayList<>();
+                }
+
+                FrameMetricsObserver fmo = new FrameMetricsObserver(window,
+                        handler.getLooper(), listener);
+                mFrameMetricsObservers.add(fmo);
+                mAttachInfo.mHardwareRenderer.addFrameMetricsObserver(fmo);
             } else {
                 Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
             }
         } else {
-            if (mPendingFrameStatsObservers == null) {
-                mPendingFrameStatsObservers = new ArrayList<>();
+            if (mFrameMetricsObservers == null) {
+                mFrameMetricsObservers = new ArrayList<>();
             }
 
-            mPendingFrameStatsObservers.add(fso);
+            FrameMetricsObserver fmo = new FrameMetricsObserver(window,
+                    handler.getLooper(), listener);
+            mFrameMetricsObservers.add(fmo);
         }
     }
 
@@ -5500,32 +5510,45 @@
      *
      * @hide
      */
-    public void removeFrameStatsObserver(FrameStatsObserver fso) {
+    public void removeFrameMetricsListener(Window.FrameMetricsListener listener) {
         ThreadedRenderer renderer = getHardwareRenderer();
-
-        if (mPendingFrameStatsObservers != null) {
-            mPendingFrameStatsObservers.remove(fso);
+        FrameMetricsObserver fmo = findFrameMetricsObserver(listener);
+        if (fmo == null) {
+            throw new IllegalArgumentException("attempt to remove FrameMetricsListener that was never added");
         }
 
-        if (renderer != null) {
-            renderer.removeFrameStatsObserver(fso);
+        if (mFrameMetricsObservers != null) {
+            mFrameMetricsObservers.remove(fmo);
+            if (renderer != null) {
+                renderer.removeFrameMetricsObserver(fmo);
+            }
         }
     }
 
-    private void registerPendingFrameStatsObservers() {
-        if (mPendingFrameStatsObservers != null) {
+    private void registerPendingFrameMetricsObservers() {
+        if (mFrameMetricsObservers != null) {
             ThreadedRenderer renderer = getHardwareRenderer();
             if (renderer != null) {
-                for (FrameStatsObserver fso : mPendingFrameStatsObservers) {
-                    renderer.addFrameStatsObserver(fso);
+                for (FrameMetricsObserver fmo : mFrameMetricsObservers) {
+                    renderer.addFrameMetricsObserver(fmo);
                 }
             } else {
                 Log.w(VIEW_LOG_TAG, "View not hardware-accelerated. Unable to observe frame stats");
             }
-            mPendingFrameStatsObservers = null;
         }
     }
 
+    private FrameMetricsObserver findFrameMetricsObserver(Window.FrameMetricsListener listener) {
+        for (int i = 0; i < mFrameMetricsObservers.size(); i++) {
+            FrameMetricsObserver observer = mFrameMetricsObservers.get(i);
+            if (observer.mListener == listener) {
+                return observer;
+            }
+        }
+
+        return null;
+    }
+
     /**
      * Call this view's OnClickListener, if it is defined.  Performs all normal
      * actions associated with clicking: reporting accessibility event, playing
@@ -15160,7 +15183,7 @@
             mFloatingTreeObserver = null;
         }
 
-        registerPendingFrameStatsObservers();
+        registerPendingFrameMetricsObservers();
 
         if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
             mAttachInfo.mScrollContainers.add(this);
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index c68a740..9f05990 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -34,6 +34,7 @@
 import android.media.session.MediaController;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.SystemProperties;
@@ -604,6 +605,34 @@
         void onRestrictedCaptionAreaChanged(Rect rect);
     }
 
+    /**
+     * Callback for clients that want frame timing information for each
+     * frame rendered by the Window.
+     */
+    public interface FrameMetricsListener {
+        /**
+         * Called when information is available for the previously rendered frame.
+         *
+         * Reports can be dropped if this callback takes too
+         * long to execute, as the report producer cannot wait for the consumer to
+         * complete.
+         *
+         * It is highly recommended that clients copy the passed in FrameMetrics
+         * via {@link FrameMetrics#FrameMetrics(FrameMetrics)} within this method and defer
+         * additional computation or storage to another thread to avoid unnecessarily
+         * dropping reports.
+         *
+         * @param window The {@link Window} on which the frame was displayed.
+         * @param frameMetrics the available metrics. This object is reused on every call
+         * and thus <strong>this reference is not valid outside the scope of this method</strong>.
+         * @param dropCountSinceLastInvocation the number of reports dropped since the last time
+         * this callback was invoked.
+         */
+        void onMetricsAvailable(Window window, FrameMetrics frameMetrics,
+                int dropCountSinceLastInvocation);
+    }
+
+
     public Window(Context context) {
         mContext = context;
         mFeatures = mLocalFeatures = getDefaultFeatures(context);
@@ -798,33 +827,28 @@
      * Set an observer to collect frame stats for each frame rendererd in this window.
      *
      * Must be in hardware rendering mode.
-     * @hide
      */
-    public final void addFrameStatsObserver(@NonNull FrameStatsObserver fso) {
+    public final void addFrameMetricsListener(@NonNull FrameMetricsListener listener,
+            Handler handler) {
         final View decorView = getDecorView();
         if (decorView == null) {
             throw new IllegalStateException("can't observe a Window without an attached view");
         }
 
-        if (fso == null) {
-            throw new NullPointerException("FrameStatsObserver cannot be null");
+        if (listener == null) {
+            throw new NullPointerException("listener cannot be null");
         }
 
-        if (fso.isRegistered()) {
-            throw new IllegalStateException("FrameStatsObserver already registered on a Window.");
-        }
-
-        decorView.addFrameStatsObserver(fso);
+        decorView.addFrameMetricsListener(this, listener, handler);
     }
 
     /**
      * Remove observer and stop listening to frame stats for this window.
-     * @hide
      */
-    public final void removeFrameStatsObserver(FrameStatsObserver fso) {
+    public final void removeFrameMetricsListener(FrameMetricsListener listener) {
         final View decorView = getDecorView();
         if (decorView != null) {
-            getDecorView().removeFrameStatsObserver(fso);
+            getDecorView().removeFrameMetricsListener(listener);
         }
     }
 
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 43306d0..d97f8af 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -38,7 +38,6 @@
 import android.util.Slog;
 import android.util.Xml;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
-import android.view.inputmethod.InputMethodSubtypeArray;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -122,7 +121,7 @@
      * @param context The Context in which we are parsing the input method.
      * @param service The ResolveInfo returned from the package manager about
      * this input method's component.
-     * @param additionalSubtypes additional subtypes being added to this InputMethodInfo
+     * @param additionalSubtypesMap additional subtypes being added to this InputMethodInfo
      * @hide
      */
     public InputMethodInfo(Context context, ResolveInfo service,
@@ -429,6 +428,18 @@
         }
     }
 
+    /**
+     * @return {@code true} if the {@link android.inputmethodservice.InputMethodService} is marked
+     * to be Encryption-Aware.
+     * @hide
+     */
+    public boolean isEncryptionAware() {
+        if (mService == null || mService.serviceInfo == null) {
+            return false;
+        }
+        return mService.serviceInfo.encryptionAware;
+    }
+
     public void dump(Printer pw, String prefix) {
         pw.println(prefix + "mId=" + mId
                 + " mSettingsActivityName=" + mSettingsActivityName
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index b2ae835..88af920 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -86,6 +86,7 @@
 import static android.view.Window.DECOR_CAPTION_SHADE_LIGHT;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -615,7 +616,7 @@
                 if (h > 0) {
                     heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                             Math.min(h, heightSize), EXACTLY);
-                } else if ((mWindow.getAttributes().flags & FLAG_FULLSCREEN) == 0) {
+                } else if ((mWindow.getAttributes().flags & FLAG_LAYOUT_IN_SCREEN) == 0) {
                     heightMeasureSpec = MeasureSpec.makeMeasureSpec(
                             heightSize - mFloatingInsets.top - mFloatingInsets.bottom, AT_MOST);
                     mApplyFloatingVerticalInsets = true;
@@ -890,10 +891,11 @@
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         final WindowManager.LayoutParams attrs = mWindow.getAttributes();
         mFloatingInsets.setEmpty();
-        if ((attrs.flags & FLAG_FULLSCREEN) == 0) {
+        if ((attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0) {
             // For dialog windows we want to make sure they don't go over the status bar or nav bar.
             // We consume the system insets and we will reuse them later during the measure phase.
-            // We allow the app to ignore this and handle insets itself by using FLAG_FULLSCREEN.
+            // We allow the app to ignore this and handle insets itself by using
+            // FLAG_LAYOUT_IN_SCREEN.
             if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) {
                 mFloatingInsets.top = insets.getSystemWindowInsetTop();
                 mFloatingInsets.bottom = insets.getSystemWindowInsetBottom();
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index acd0501..dd0e456 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -41,6 +41,7 @@
 #include <Animator.h>
 #include <AnimationContext.h>
 #include <FrameInfo.h>
+#include <FrameMetricsObserver.h>
 #include <IContextFactory.h>
 #include <JankTracker.h>
 #include <RenderNode.h>
@@ -56,10 +57,11 @@
 using namespace android::uirenderer::renderthread;
 
 struct {
-    jfieldID buffer;
+    jfieldID frameMetrics;
+    jfieldID timingDataBuffer;
     jfieldID messageQueue;
-    jmethodID notifyData;
-} gFrameStatsObserverClassInfo;
+    jmethodID callback;
+} gFrameMetricsObserverClassInfo;
 
 static JNIEnv* getenv(JavaVM* vm) {
     JNIEnv* env;
@@ -239,31 +241,46 @@
         mBuffer = buffer;
     }
 
+    void setDropCount(int dropCount) {
+        mDropCount = dropCount;
+    }
+
     virtual void handleMessage(const Message& message);
 
 private:
     JavaVM* mVm;
 
     sp<ObserverProxy> mObserver;
-    BufferPool::Buffer* mBuffer;
+    BufferPool::Buffer* mBuffer = nullptr;
+    int mDropCount = 0;
 };
 
-class ObserverProxy : public FrameStatsObserver {
+static jlongArray get_metrics_buffer(JNIEnv* env, jobject observer) {
+    jobject frameMetrics = env->GetObjectField(
+            observer, gFrameMetricsObserverClassInfo.frameMetrics);
+    LOG_ALWAYS_FATAL_IF(frameMetrics == nullptr, "unable to retrieve data sink object");
+    jobject buffer = env->GetObjectField(
+            frameMetrics, gFrameMetricsObserverClassInfo.timingDataBuffer);
+    LOG_ALWAYS_FATAL_IF(buffer == nullptr, "unable to retrieve data sink buffer");
+    return reinterpret_cast<jlongArray>(buffer);
+}
+
+class ObserverProxy : public FrameMetricsObserver {
 public:
-    ObserverProxy(JavaVM *vm, jobject fso) : mVm(vm) {
+    ObserverProxy(JavaVM *vm, jobject observer) : mVm(vm) {
         JNIEnv* env = getenv(mVm);
 
-        jlongArray longArrayLocal = env->NewLongArray(kBufferSize);
-        LOG_ALWAYS_FATAL_IF(longArrayLocal == nullptr,
-                "OOM: can't allocate frame stats buffer");
-        env->SetObjectField(fso, gFrameStatsObserverClassInfo.buffer, longArrayLocal);
-
-        mFsoWeak = env->NewWeakGlobalRef(fso);
-        LOG_ALWAYS_FATAL_IF(mFsoWeak == nullptr,
+        mObserverWeak = env->NewWeakGlobalRef(observer);
+        LOG_ALWAYS_FATAL_IF(mObserverWeak == nullptr,
                 "unable to create frame stats observer reference");
 
-        jobject messageQueueLocal =
-                env->GetObjectField(fso, gFrameStatsObserverClassInfo.messageQueue);
+        jlongArray buffer = get_metrics_buffer(env, observer);
+        jsize bufferSize = env->GetArrayLength(reinterpret_cast<jarray>(buffer));
+        LOG_ALWAYS_FATAL_IF(bufferSize != kBufferSize,
+                "Mismatched Java/Native FrameMetrics data format.");
+
+        jobject messageQueueLocal = env->GetObjectField(
+                observer, gFrameMetricsObserverClassInfo.messageQueue);
         mMessageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueLocal);
         LOG_ALWAYS_FATAL_IF(mMessageQueue == nullptr, "message queue not available");
 
@@ -274,17 +291,18 @@
 
     ~ObserverProxy() {
         JNIEnv* env = getenv(mVm);
-        env->DeleteWeakGlobalRef(mFsoWeak);
+        env->DeleteWeakGlobalRef(mObserverWeak);
     }
 
-    jweak getJavaObjectRef() {
-        return mFsoWeak;
+    jweak getObserverReference() {
+        return mObserverWeak;
     }
 
-    virtual void notify(BufferPool::Buffer* buffer) {
+    virtual void notify(BufferPool::Buffer* buffer, int dropCount) {
         buffer->incRef();
         mMessageHandler->setBuffer(buffer);
         mMessageHandler->setObserver(this);
+        mMessageHandler->setDropCount(dropCount);
         mMessageQueue->getLooper()->sendMessage(mMessageHandler, mMessage);
     }
 
@@ -292,26 +310,27 @@
     static const int kBufferSize = static_cast<int>(FrameInfoIndex::NumIndexes);
 
     JavaVM* mVm;
-    jweak mFsoWeak;
+    jweak mObserverWeak;
+    jobject mJavaBufferGlobal;
 
     sp<MessageQueue> mMessageQueue;
     sp<NotifyHandler> mMessageHandler;
     Message mMessage;
+
 };
 
 void NotifyHandler::handleMessage(const Message& message) {
     JNIEnv* env = getenv(mVm);
 
-    jobject target = env->NewLocalRef(mObserver->getJavaObjectRef());
+    jobject target = env->NewLocalRef(mObserver->getObserverReference());
 
     if (target != nullptr) {
-        jobject javaBuffer = env->GetObjectField(target, gFrameStatsObserverClassInfo.buffer);
-        if (javaBuffer != nullptr) {
-            env->SetLongArrayRegion(reinterpret_cast<jlongArray>(javaBuffer),
-                    0, mBuffer->getSize(), mBuffer->getBuffer());
-            env->CallVoidMethod(target, gFrameStatsObserverClassInfo.notifyData);
-            env->DeleteLocalRef(target);
-        }
+        jlongArray javaBuffer = get_metrics_buffer(env, target);
+        env->SetLongArrayRegion(javaBuffer,
+                0, mBuffer->getSize(), mBuffer->getBuffer());
+        env->CallVoidMethod(target, gFrameMetricsObserverClassInfo.callback,
+                mDropCount);
+        env->DeleteLocalRef(target);
     }
 
     mBuffer->release();
@@ -579,10 +598,10 @@
 }
 
 // ----------------------------------------------------------------------------
-// FrameStatsObserver
+// FrameMetricsObserver
 // ----------------------------------------------------------------------------
 
-static jlong android_view_ThreadedRenderer_addFrameStatsObserver(JNIEnv* env,
+static jlong android_view_ThreadedRenderer_addFrameMetricsObserver(JNIEnv* env,
         jclass clazz, jlong proxyPtr, jobject fso) {
     JavaVM* vm = nullptr;
     if (env->GetJavaVM(&vm) != JNI_OK) {
@@ -593,25 +612,18 @@
     renderthread::RenderProxy* renderProxy =
             reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
 
-    FrameStatsObserver* observer = new ObserverProxy(vm, fso);
-    renderProxy->addFrameStatsObserver(observer);
+    FrameMetricsObserver* observer = new ObserverProxy(vm, fso);
+    renderProxy->addFrameMetricsObserver(observer);
     return reinterpret_cast<jlong>(observer);
 }
 
-static void android_view_ThreadedRenderer_removeFrameStatsObserver(JNIEnv* env, jclass clazz,
+static void android_view_ThreadedRenderer_removeFrameMetricsObserver(JNIEnv* env, jclass clazz,
         jlong proxyPtr, jlong observerPtr) {
-    FrameStatsObserver* observer = reinterpret_cast<FrameStatsObserver*>(observerPtr);
+    FrameMetricsObserver* observer = reinterpret_cast<FrameMetricsObserver*>(observerPtr);
     renderthread::RenderProxy* renderProxy =
             reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
 
-    renderProxy->removeFrameStatsObserver(observer);
-}
-
-static jint android_view_ThreadedRenderer_getDroppedFrameReportCount(JNIEnv* env, jclass clazz,
-        jlong proxyPtr) {
-    renderthread::RenderProxy* renderProxy =
-            reinterpret_cast<renderthread::RenderProxy*>(proxyPtr);
-    return renderProxy->getDroppedFrameReportCount();
+    renderProxy->removeFrameMetricsObserver(observer);
 }
 
 // ----------------------------------------------------------------------------
@@ -684,25 +696,26 @@
     { "nRemoveRenderNode", "(JJ)V", (void*) android_view_ThreadedRenderer_removeRenderNode},
     { "nDrawRenderNode", "(JJ)V", (void*) android_view_ThreadedRendererd_drawRenderNode},
     { "nSetContentDrawBounds", "(JIIII)V", (void*)android_view_ThreadedRenderer_setContentDrawBounds},
-    { "nAddFrameStatsObserver",
-            "(JLandroid/view/FrameStatsObserver;)J",
-            (void*)android_view_ThreadedRenderer_addFrameStatsObserver },
-    { "nRemoveFrameStatsObserver",
+    { "nAddFrameMetricsObserver",
+            "(JLandroid/view/FrameMetricsObserver;)J",
+            (void*)android_view_ThreadedRenderer_addFrameMetricsObserver },
+    { "nRemoveFrameMetricsObserver",
             "(JJ)V",
-            (void*)android_view_ThreadedRenderer_removeFrameStatsObserver },
-    { "nGetDroppedFrameReportCount",
-            "(J)J",
-            (void*)android_view_ThreadedRenderer_getDroppedFrameReportCount },
+            (void*)android_view_ThreadedRenderer_removeFrameMetricsObserver },
 };
 
 int register_android_view_ThreadedRenderer(JNIEnv* env) {
-    jclass clazz = FindClassOrDie(env, "android/view/FrameStatsObserver");
-    gFrameStatsObserverClassInfo.messageQueue  =
-            GetFieldIDOrDie(env, clazz, "mMessageQueue", "Landroid/os/MessageQueue;");
-    gFrameStatsObserverClassInfo.buffer =
-            GetFieldIDOrDie(env, clazz, "mBuffer", "[J");
-    gFrameStatsObserverClassInfo.notifyData =
-            GetMethodIDOrDie(env, clazz, "notifyDataAvailable", "()V");
+    jclass observerClass = FindClassOrDie(env, "android/view/FrameMetricsObserver");
+    gFrameMetricsObserverClassInfo.frameMetrics = GetFieldIDOrDie(
+            env, observerClass, "mFrameMetrics", "Landroid/view/FrameMetrics;");
+    gFrameMetricsObserverClassInfo.messageQueue = GetFieldIDOrDie(
+            env, observerClass, "mMessageQueue", "Landroid/os/MessageQueue;");
+    gFrameMetricsObserverClassInfo.callback = GetMethodIDOrDie(
+            env, observerClass, "notifyDataAvailable", "(I)V");
+
+    jclass metricsClass = FindClassOrDie(env, "android/view/FrameMetrics");
+    gFrameMetricsObserverClassInfo.timingDataBuffer = GetFieldIDOrDie(
+            env, metricsClass, "mTimingData", "[J");
 
     return RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1db75e6..99daab4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -425,6 +425,8 @@
 
     <protected-broadcast android:name="android.intent.action.DYNAMIC_SENSOR_CHANGED" />
 
+    <protected-broadcast android:name="android.intent.action.ACTION_RADIO_OFF" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
diff --git a/core/res/res/layout/language_picker_section_header.xml b/core/res/res/layout/language_picker_section_header.xml
index c4d3069..b12ec8c 100644
--- a/core/res/res/layout/language_picker_section_header.xml
+++ b/core/res/res/layout/language_picker_section_header.xml
@@ -19,7 +19,8 @@
           style="?android:attr/preferenceCategoryStyle"
           android:layout_width="match_parent"
           android:layout_height="36dp"
-          android:paddingStart="12dp"
-          android:paddingEnd="12dp"
+          android:gravity="center_vertical"
+          android:paddingStart="18dp"
+          android:paddingEnd="18dp"
           android:textColor="?android:attr/colorAccent"
           tools:text="@string/language_picker_section_all"/>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 3a5336c..1496d09 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -429,8 +429,10 @@
          sets. -->
     <attr name="multiArch" format ="boolean" />
 
-    <!-- Specify abiOverride for multiArch application. -->
-    <attr name="abiOverride" />
+    <!-- Specify whether the 32 bit version of the ABI should be used in a
+         multiArch application. If both abioverride flag (i.e. using abi option of abd install)
+         and use32bitAbi are used, then use32bit is ignored.-->
+    <attr name="use32bitAbi" />
 
     <!-- Specify whether a component is allowed to have multiple instances
          of itself running in different processes.  Use with the activity
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5c5aff0..69d005c 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2696,7 +2696,7 @@
     <public type="attr" name="endX" />
     <public type="attr" name="endY" />
     <public type="attr" name="offset" />
-    <public type="attr" name="abiOverride" />
+    <public type="attr" name="use32bitAbi" />
     <public type="attr" name="bitmap" />
     <public type="attr" name="hotSpotX" />
     <public type="attr" name="hotSpotY" />
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
index d133a12..93581db 100644
--- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodUtilsTest.java
@@ -20,11 +20,13 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.res.Configuration;
 import android.os.Parcel;
 import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.LocaleList;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
 import android.view.inputmethod.InputMethodSubtype;
@@ -230,7 +232,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_EN_US, imi);
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_EN_US))
+                                    .getResources(),
+                            imi);
             assertEquals(1, result.size());
             verifyEquality(autoSubtype, result.get(0));
         }
@@ -251,8 +256,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_EN_US, imi);
-            assertEquals(1, result.size());
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_EN_US))
+                                    .getResources(),
+                            imi);
             verifyEquality(nonAutoEnUS, result.get(0));
         }
 
@@ -271,7 +278,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_EN_GB, imi);
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_EN_GB))
+                                    .getResources(),
+                            imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoEnGB, result.get(0));
         }
@@ -292,7 +302,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_FR, imi);
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_FR))
+                                    .getResources(),
+                            imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoFrCA, result.get(0));
         }
@@ -309,7 +322,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_FR_CA, imi);
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_FR_CA))
+                                    .getResources(),
+                            imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoFrCA, result.get(0));
         }
@@ -327,7 +343,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_JA_JP, imi);
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_JA_JP))
+                                    .getResources(),
+                            imi);
             assertEquals(3, result.size());
             verifyEquality(nonAutoJa, result.get(0));
             verifyEquality(nonAutoEnabledWhenDefaultIsNotAsciiCalableSubtype, result.get(1));
@@ -344,7 +363,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_FIL_PH, imi);
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_FIL_PH))
+                                    .getResources(),
+                            imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoFil, result.get(0));
         }
@@ -361,7 +383,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_FI, imi);
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_FI))
+                                    .getResources(),
+                            imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoJa, result.get(0));
         }
@@ -376,7 +401,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_IN, imi);
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_IN))
+                                    .getResources(),
+                            imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoIn, result.get(0));
         }
@@ -389,7 +417,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_ID, imi);
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_ID))
+                                    .getResources(),
+                            imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoIn, result.get(0));
         }
@@ -402,7 +433,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_IN, imi);
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_IN))
+                                    .getResources(),
+                            imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoId, result.get(0));
         }
@@ -415,7 +449,10 @@
                     "com.android.apps.inputmethod.latin", "DummyLatinIme", !IS_AUX, IS_DEFAULT,
                     subtypes);
             final ArrayList<InputMethodSubtype> result =
-                    callGetImplicitlyApplicableSubtypesLockedWithLocale(LOCALE_ID, imi);
+                    InputMethodUtils.getImplicitlyApplicableSubtypesLocked(
+                            createTargetContextWithLocales(new LocaleList(LOCALE_ID))
+                                    .getResources(),
+                            imi);
             assertEquals(1, result.size());
             verifyEquality(nonAutoId, result.get(0));
         }
@@ -568,24 +605,11 @@
         }
     }
 
-    private ArrayList<InputMethodSubtype> callGetImplicitlyApplicableSubtypesLockedWithLocale(
-            final Locale locale, final InputMethodInfo imi) {
-        final Context context = getInstrumentation().getTargetContext();
-        final Locale initialLocale = context.getResources().getConfiguration().locale;
-        try {
-            context.getResources().getConfiguration().setLocale(locale);
-            return InputMethodUtils.getImplicitlyApplicableSubtypesLocked(context.getResources(),
-                    imi);
-        } finally {
-            context.getResources().getConfiguration().setLocale(initialLocale);
-        }
-    }
-
     private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes,
             final Locale systemLocale, final boolean isSystemReady, String... expectedImeNames) {
-        final Context context = getInstrumentation().getTargetContext();
-        final String[] actualImeNames = getPackageNames(callGetDefaultEnabledImesWithLocale(
-                context, isSystemReady, preinstalledImes, systemLocale));
+        final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
+        final String[] actualImeNames = getPackageNames(
+                InputMethodUtils.getDefaultEnabledImes(context, isSystemReady, preinstalledImes));
         assertEquals(expectedImeNames.length, actualImeNames.length);
         for (int i = 0; i < expectedImeNames.length; ++i) {
             assertEquals(expectedImeNames[i], actualImeNames[i]);
@@ -606,16 +630,12 @@
         }
     }
 
-    private static ArrayList<InputMethodInfo> callGetDefaultEnabledImesWithLocale(
-            final Context context, final boolean isSystemReady,
-            final ArrayList<InputMethodInfo> imis, final Locale locale) {
-        final Locale initialLocale = context.getResources().getConfiguration().locale;
-        try {
-            context.getResources().getConfiguration().setLocale(locale);
-            return InputMethodUtils.getDefaultEnabledImes(context, isSystemReady, imis);
-        } finally {
-            context.getResources().getConfiguration().setLocale(initialLocale);
-        }
+    private Context createTargetContextWithLocales(final LocaleList locales) {
+        final Configuration resourceConfiguration = new Configuration();
+        resourceConfiguration.setLocales(locales);
+        return getInstrumentation()
+                .getTargetContext()
+                .createConfigurationContext(resourceConfiguration);
     }
 
     private String[] getPackageNames(final ArrayList<InputMethodInfo> imis) {
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index c8333c8..302b0bd 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -20,9 +20,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
+
 import com.android.org.bouncycastle.util.io.pem.PemObject;
 import com.android.org.bouncycastle.util.io.pem.PemReader;
 import com.android.org.bouncycastle.util.io.pem.PemWriter;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -147,20 +149,23 @@
         Reader reader = new InputStreamReader(bai, StandardCharsets.US_ASCII);
         PemReader pr = new PemReader(reader);
 
-        CertificateFactory cf = CertificateFactory.getInstance("X509");
+        try {
+            CertificateFactory cf = CertificateFactory.getInstance("X509");
 
-        List<X509Certificate> result = new ArrayList<X509Certificate>();
-        PemObject o;
-        while ((o = pr.readPemObject()) != null) {
-            if (o.getType().equals("CERTIFICATE")) {
-                Certificate c = cf.generateCertificate(new ByteArrayInputStream(o.getContent()));
-                result.add((X509Certificate) c);
-            } else {
-                throw new IllegalArgumentException("Unknown type " + o.getType());
+            List<X509Certificate> result = new ArrayList<X509Certificate>();
+            PemObject o;
+            while ((o = pr.readPemObject()) != null) {
+                if (o.getType().equals("CERTIFICATE")) {
+                    Certificate c = cf.generateCertificate(new ByteArrayInputStream(o.getContent()));
+                    result.add((X509Certificate) c);
+                } else {
+                    throw new IllegalArgumentException("Unknown type " + o.getType());
+                }
             }
+            return result;
+        } finally {
+            pr.close();
         }
-        pr.close();
-        return result;
     }
 
     private static Credentials singleton;
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index 1b87a41..3090ac1 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -19,7 +19,6 @@
 import android.app.ActivityThread;
 import android.app.Application;
 import android.app.KeyguardManager;
-
 import android.content.Context;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
@@ -32,6 +31,7 @@
 import android.security.keymaster.KeyCharacteristics;
 import android.security.keymaster.KeymasterArguments;
 import android.security.keymaster.KeymasterBlob;
+import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keymaster.KeymasterDefs;
 import android.security.keymaster.OperationResult;
 import android.security.keystore.KeyExpiredException;
@@ -615,6 +615,17 @@
         return onUserPasswordChanged(UserHandle.getUserId(Process.myUid()), newPassword);
     }
 
+    public int attestKey(
+            String alias, KeymasterArguments params, KeymasterCertificateChain outChain) {
+        try {
+            return mBinder.attestKey(alias, params, outChain);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Cannot connect to keystore", e);
+            return SYSTEM_ERROR;
+        }
+    }
+
+
     /**
      * Returns a {@link KeyStoreException} corresponding to the provided keystore/keymaster error
      * code.
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 65460b5..3a0ff1c 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -22,6 +22,7 @@
 import android.security.KeyStore;
 import android.security.keymaster.KeyCharacteristics;
 import android.security.keymaster.KeymasterArguments;
+import android.security.keymaster.KeymasterCertificateChain;
 import android.security.keymaster.KeymasterDefs;
 
 import com.android.org.bouncycastle.asn1.ASN1EncodableVector;
@@ -46,6 +47,8 @@
 
 import libcore.util.EmptyArray;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.math.BigInteger;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.KeyPair;
@@ -57,14 +60,17 @@
 import java.security.SecureRandom;
 import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.ECGenParameterSpec;
 import java.security.spec.RSAKeyGenParameterSpec;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -166,6 +172,7 @@
         mOriginalKeymasterAlgorithm = keymasterAlgorithm;
     }
 
+    @SuppressWarnings("deprecation")
     @Override
     public void initialize(int keysize, SecureRandom random) {
         throw new IllegalArgumentException(
@@ -173,6 +180,7 @@
                 + " required to initialize this KeyPairGenerator");
     }
 
+    @SuppressWarnings("deprecation")
     @Override
     public void initialize(AlgorithmParameterSpec params, SecureRandom random)
             throws InvalidAlgorithmParameterException {
@@ -447,6 +455,69 @@
                     + ", but the user has not yet entered the credential");
         }
 
+        byte[] additionalEntropy =
+                KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
+                        mRng, (mKeySizeBits + 7) / 8);
+
+        Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
+        final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias;
+        boolean success = false;
+        try {
+            generateKeystoreKeyPair(
+                    privateKeyAlias, constructKeyGenerationArguments(), additionalEntropy, flags);
+            KeyPair keyPair = loadKeystoreKeyPair(privateKeyAlias);
+
+            storeCertificateChain(flags, createCertificateChain(privateKeyAlias, keyPair));
+
+            success = true;
+            return keyPair;
+        } finally {
+            if (!success) {
+                Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
+            }
+        }
+    }
+
+    private Iterable<byte[]> createCertificateChain(final String privateKeyAlias, KeyPair keyPair)
+            throws ProviderException {
+        byte[] challenge = mSpec.getAttestationChallenge();
+        if (challenge != null) {
+            KeymasterArguments args = new KeymasterArguments();
+            args.addBytes(KeymasterDefs.KM_TAG_ATTESTATION_CHALLENGE, challenge);
+            return getAttestationChain(privateKeyAlias, keyPair, args);
+        }
+
+        // Very short certificate chain in the non-attestation case.
+        return Collections.singleton(generateSelfSignedCertificateBytes(keyPair));
+    }
+
+    private void generateKeystoreKeyPair(final String privateKeyAlias, KeymasterArguments args,
+            byte[] additionalEntropy, final int flags) throws ProviderException {
+        KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
+        int errorCode = mKeyStore.generateKey(privateKeyAlias, args, additionalEntropy,
+                mEntryUid, flags, resultingKeyCharacteristics);
+        if (errorCode != KeyStore.NO_ERROR) {
+            throw new ProviderException(
+                    "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
+        }
+    }
+
+    private KeyPair loadKeystoreKeyPair(final String privateKeyAlias) throws ProviderException {
+        try {
+            KeyPair result  = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
+                    mKeyStore, privateKeyAlias, mEntryUid);
+            if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) {
+                throw new ProviderException(
+                        "Generated key pair algorithm does not match requested algorithm: "
+                                + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm);
+            }
+            return result;
+        } catch (UnrecoverableKeyException e) {
+            throw new ProviderException("Failed to load generated key pair from keystore", e);
+        }
+    }
+
+    private KeymasterArguments constructKeyGenerationArguments() {
         KeymasterArguments args = new KeymasterArguments();
         args.addUnsignedInt(KeymasterDefs.KM_TAG_KEY_SIZE, mKeySizeBits);
         args.addEnum(KeymasterDefs.KM_TAG_ALGORITHM, mKeymasterAlgorithm);
@@ -466,73 +537,72 @@
                 mSpec.getKeyValidityForConsumptionEnd());
         addAlgorithmSpecificParameters(args);
 
-        byte[] additionalEntropy =
-                KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
-                        mRng, (mKeySizeBits + 7) / 8);
+        if (mSpec.isUniqueIdIncluded())
+            args.addBoolean(KeymasterDefs.KM_TAG_INCLUDE_UNIQUE_ID);
 
-        final String privateKeyAlias = Credentials.USER_PRIVATE_KEY + mEntryAlias;
-        boolean success = false;
-        try {
-            Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
-            KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
-            int errorCode = mKeyStore.generateKey(
-                    privateKeyAlias,
-                    args,
-                    additionalEntropy,
-                    mEntryUid,
-                    flags,
-                    resultingKeyCharacteristics);
-            if (errorCode != KeyStore.NO_ERROR) {
-                throw new ProviderException(
-                        "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
-            }
+        return args;
+    }
 
-            KeyPair result;
-            try {
-                result = AndroidKeyStoreProvider.loadAndroidKeyStoreKeyPairFromKeystore(
-                        mKeyStore, privateKeyAlias, mEntryUid);
-            } catch (UnrecoverableKeyException e) {
-                throw new ProviderException("Failed to load generated key pair from keystore", e);
-            }
+    private void storeCertificateChain(final int flags, Iterable<byte[]> iterable)
+            throws ProviderException {
+        Iterator<byte[]> iter = iterable.iterator();
+        storeCertificate(
+                Credentials.USER_CERTIFICATE, iter.next(), flags, "Failed to store certificate");
 
-            if (!mJcaKeyAlgorithm.equalsIgnoreCase(result.getPrivate().getAlgorithm())) {
-                throw new ProviderException(
-                        "Generated key pair algorithm does not match requested algorithm: "
-                        + result.getPrivate().getAlgorithm() + " vs " + mJcaKeyAlgorithm);
-            }
-
-            final X509Certificate cert;
-            try {
-                cert = generateSelfSignedCertificate(result.getPrivate(), result.getPublic());
-            } catch (Exception e) {
-                throw new ProviderException("Failed to generate self-signed certificate", e);
-            }
-
-            byte[] certBytes;
-            try {
-                certBytes = cert.getEncoded();
-            } catch (CertificateEncodingException e) {
-                throw new ProviderException(
-                        "Failed to obtain encoded form of self-signed certificate", e);
-            }
-
-            int insertErrorCode = mKeyStore.insert(
-                    Credentials.USER_CERTIFICATE + mEntryAlias,
-                    certBytes,
-                    mEntryUid,
-                    flags);
-            if (insertErrorCode != KeyStore.NO_ERROR) {
-                throw new ProviderException("Failed to store self-signed certificate",
-                        KeyStore.getKeyStoreException(insertErrorCode));
-            }
-
-            success = true;
-            return result;
-        } finally {
-            if (!success) {
-                Credentials.deleteAllTypesForAlias(mKeyStore, mEntryAlias, mEntryUid);
-            }
+        if (!iter.hasNext()) {
+            return;
         }
+
+        ByteArrayOutputStream certificateConcatenationStream = new ByteArrayOutputStream();
+        while (iter.hasNext()) {
+            byte[] data = iter.next();
+            certificateConcatenationStream.write(data, 0, data.length);
+        }
+
+        storeCertificate(Credentials.CA_CERTIFICATE, certificateConcatenationStream.toByteArray(),
+                flags, "Failed to store attestation CA certificate");
+    }
+
+    private void storeCertificate(String prefix, byte[] certificateBytes, final int flags,
+            String failureMessage) throws ProviderException {
+        int insertErrorCode = mKeyStore.insert(
+                prefix + mEntryAlias,
+                certificateBytes,
+                mEntryUid,
+                flags);
+        if (insertErrorCode != KeyStore.NO_ERROR) {
+            throw new ProviderException(failureMessage,
+                    KeyStore.getKeyStoreException(insertErrorCode));
+        }
+    }
+
+    private byte[] generateSelfSignedCertificateBytes(KeyPair keyPair) throws ProviderException {
+        try {
+            return generateSelfSignedCertificate(keyPair.getPrivate(), keyPair.getPublic())
+                    .getEncoded();
+        } catch (IOException | CertificateParsingException e) {
+            throw new ProviderException("Failed to generate self-signed certificate", e);
+        } catch (CertificateEncodingException e) {
+            throw new ProviderException(
+                    "Failed to obtain encoded form of self-signed certificate", e);
+        }
+    }
+
+    private Iterable<byte[]> getAttestationChain(String privateKeyAlias,
+            KeyPair keyPair, KeymasterArguments args)
+                    throws ProviderException {
+        KeymasterCertificateChain outChain = new KeymasterCertificateChain();
+        int errorCode = mKeyStore.attestKey(privateKeyAlias, args, outChain);
+        if (errorCode != KeyStore.NO_ERROR) {
+            throw new ProviderException("Failed to generate attestation certificate chain",
+                    KeyStore.getKeyStoreException(errorCode));
+        }
+        Collection<byte[]> chain = outChain.getCertificates();
+        if (chain.size() < 2) {
+            throw new ProviderException("Attestation certificate chain contained "
+                    + chain.size() + " entries. At least two are required.");
+        }
+        return chain;
     }
 
     private void addAlgorithmSpecificParameters(KeymasterArguments keymasterArgs) {
@@ -548,8 +618,8 @@
         }
     }
 
-    private X509Certificate generateSelfSignedCertificate(
-            PrivateKey privateKey, PublicKey publicKey) throws Exception {
+    private X509Certificate generateSelfSignedCertificate(PrivateKey privateKey,
+            PublicKey publicKey) throws CertificateParsingException, IOException {
         String signatureAlgorithm =
                 getCertificateSignatureAlgorithm(mKeymasterAlgorithm, mKeySizeBits, mSpec);
         if (signatureAlgorithm == null) {
@@ -587,7 +657,7 @@
 
     @SuppressWarnings("deprecation")
     private X509Certificate generateSelfSignedCertificateWithFakeSignature(
-            PublicKey publicKey) throws Exception {
+            PublicKey publicKey) throws IOException, CertificateParsingException {
         V3TBSCertificateGenerator tbsGenerator = new V3TBSCertificateGenerator();
         ASN1ObjectIdentifier sigAlgOid;
         AlgorithmIdentifier sigAlgId;
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index add199f..f3fd129 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -250,6 +250,8 @@
     private final boolean mRandomizedEncryptionRequired;
     private final boolean mUserAuthenticationRequired;
     private final int mUserAuthenticationValidityDurationSeconds;
+    private final byte[] mAttestationChallenge;
+    private final boolean mUniqueIdIncluded;
 
     /**
      * @hide should be built with Builder
@@ -273,7 +275,9 @@
             @KeyProperties.BlockModeEnum String[] blockModes,
             boolean randomizedEncryptionRequired,
             boolean userAuthenticationRequired,
-            int userAuthenticationValidityDurationSeconds) {
+            int userAuthenticationValidityDurationSeconds,
+            byte[] attestationChallenge,
+            boolean uniqueIdIncluded) {
         if (TextUtils.isEmpty(keyStoreAlias)) {
             throw new IllegalArgumentException("keyStoreAlias must not be empty");
         }
@@ -315,6 +319,8 @@
         mRandomizedEncryptionRequired = randomizedEncryptionRequired;
         mUserAuthenticationRequired = userAuthenticationRequired;
         mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
+        mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
+        mUniqueIdIncluded = uniqueIdIncluded;
     }
 
     /**
@@ -539,6 +545,48 @@
     }
 
     /**
+     * Returns the attestation challenge value that will be placed in attestation certificate for
+     * this key pair.
+     *
+     * <p>If this method returns non-{@code null}, the public key certificate for this key pair will
+     * contain an extension that describes the details of the key's configuration and
+     * authorizations, including the content of the attestation challenge value. If the key is in
+     * secure hardware, and if the secure hardware supports attestation, the certificate will be
+     * signed by a chain of certificates rooted at a trustworthy CA key. Otherwise the chain will
+     * be rooted at an untrusted certificate.
+     *
+     * <p>If this method returns {@code null}, and the spec is used to generate an asymmetric (RSA
+     * or EC) key pair, the public key will have a self-signed certificate if it has purpose {@link
+     * KeyProperties#PURPOSE_SIGN} (see {@link #KeyGenParameterSpec(String, int)). If does not have
+     * purpose {@link KeyProperties#PURPOSE_SIGN}, it will have a fake certificate.
+     *
+     * <p>Symmetric keys, such as AES and HMAC keys, do not have public key certificates. If a
+     * {@link KeyGenParameterSpec} with {@link #hasAttestationCertificate()} returning
+     * non-{@code null} is used to generate a symmetric (AES or HMAC) key,
+     * {@link KeyGenerator#generateKey())} will throw
+     * {@link java.security.InvalidAlgorithmParameterException}.
+     *
+     * @see Builder#setAttestationChallenge(byte[])
+     */
+    /*
+     * TODO(swillden): Update this documentation to describe the hardware and software root keys,
+     * including information about CRL/OCSP services for discovering revocations, and to link to
+     * documentation of the extension format and content.
+     */
+    public byte[] getAttestationChallenge() {
+        return Utils.cloneIfNotNull(mAttestationChallenge);
+    }
+
+    /**
+     * @hide This is a system-only API
+     *
+     * Returns {@code true} if the attestation certificate will contain a unique ID field.
+     */
+    public boolean isUniqueIdIncluded() {
+        return mUniqueIdIncluded;
+    }
+
+    /**
      * Builder of {@link KeyGenParameterSpec} instances.
      */
     public final static class Builder {
@@ -562,6 +610,8 @@
         private boolean mRandomizedEncryptionRequired = true;
         private boolean mUserAuthenticationRequired;
         private int mUserAuthenticationValidityDurationSeconds = -1;
+        private byte[] mAttestationChallenge = null;
+        private boolean mUniqueIdIncluded = false;
 
         /**
          * Creates a new instance of the {@code Builder}.
@@ -957,6 +1007,59 @@
             return this;
         }
 
+        /*
+         * TODO(swillden): Update this documentation to describe the hardware and software root
+         * keys, including information about CRL/OCSP services for discovering revocations, and to
+         * link to documentation of the extension format and content.
+         */
+        /**
+         * Sets whether an attestation certificate will be generated for this key pair, and what
+         * challenge value will be placed in the certificate.  The attestation certificate chain
+         * can be retrieved with with {@link java.security.KeyStore#getCertificateChain(String)}.
+         *
+         * <p>If {@code attestationChallenge} is not {@code null}, the public key certificate for
+         * this key pair will contain an extension that describes the details of the key's
+         * configuration and authorizations, including the {@code attestationChallenge} value. If
+         * the key is in secure hardware, and if the secure hardware supports attestation, the
+         * certificate will be signed by a chain of certificates rooted at a trustworthy CA key.
+         * Otherwise the chain will be rooted at an untrusted certificate.
+         *
+         * <p>The purpose of the challenge value is to enable relying parties to verify that the key
+         * was created in response to a specific request. If attestation is desired but no
+         * challenged is needed, any non-{@code null} value may be used, including an empty byte
+         * array.
+         *
+         * <p>If {@code attestationChallenge} is {@code null}, and this spec is used to generate an
+         * asymmetric (RSA or EC) key pair, the public key certificate will be self-signed if the
+         * key has purpose {@link KeyProperties#PURPOSE_SIGN} (see
+         * {@link #KeyGenParameterSpec(String, int)). If the key does not have purpose
+         * {@link KeyProperties#PURPOSE_SIGN}, it is not possible to use the key to sign a
+         * certificate, so the public key certificate will contain a dummy signature.
+         *
+         * <p>Symmetric keys, such as AES and HMAC keys, do not have public key certificates. If a
+         * {@code getAttestationChallenge} returns non-{@code null} and the spec is used to
+         * generate a symmetric (AES or HMAC) key, {@link KeyGenerator#generateKey()} will throw
+         * {@link java.security.InvalidAlgorithmParameterException}.
+         *
+         * @see Builder#setAttestationChallenge(String attestationChallenge)
+         */
+        @NonNull
+        public Builder setAttestationChallenge(byte[] attestationChallenge) {
+            mAttestationChallenge = attestationChallenge;
+            return this;
+        }
+
+        /**
+         * @hide Only system apps can use this method.
+         *
+         * Sets whether to include a temporary unique ID field in the attestation certificate.
+         */
+        @NonNull
+        public Builder setUniqueIdIncluded(boolean uniqueIdIncluded) {
+            mUniqueIdIncluded = uniqueIdIncluded;
+            return this;
+        }
+
         /**
          * Builds an instance of {@code KeyGenParameterSpec}.
          */
@@ -981,7 +1084,9 @@
                     mBlockModes,
                     mRandomizedEncryptionRequired,
                     mUserAuthenticationRequired,
-                    mUserAuthenticationValidityDurationSeconds);
+                    mUserAuthenticationValidityDurationSeconds,
+                    mAttestationChallenge,
+                    mUniqueIdIncluded);
         }
     }
 }
diff --git a/keystore/java/android/security/keystore/Utils.java b/keystore/java/android/security/keystore/Utils.java
index 9bec682..5722c7b 100644
--- a/keystore/java/android/security/keystore/Utils.java
+++ b/keystore/java/android/security/keystore/Utils.java
@@ -29,4 +29,8 @@
     static Date cloneIfNotNull(Date value) {
         return (value != null) ? (Date) value.clone() : null;
     }
+
+    static byte[] cloneIfNotNull(byte[] value) {
+        return (value != null) ? value.clone() : null;
+    }
 }
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
index 9c08b4d..e368537 100644
--- a/libs/hwui/ClipArea.cpp
+++ b/libs/hwui/ClipArea.cpp
@@ -361,17 +361,21 @@
             "expect RectangleList to be trivially destructible");
 
     if (mLastSerialization == nullptr) {
+        ClipBase* serialization = nullptr;
         switch (mMode) {
         case ClipMode::Rectangle:
-            mLastSerialization = allocator.create<ClipRect>(mClipRect);
+            serialization = allocator.create<ClipRect>(mClipRect);
             break;
         case ClipMode::RectangleList:
-            mLastSerialization = allocator.create<ClipRectList>(mRectangleList);
+            serialization = allocator.create<ClipRectList>(mRectangleList);
+            serialization->rect = mRectangleList.calculateBounds();
             break;
         case ClipMode::Region:
-            mLastSerialization = allocator.create<ClipRegion>(mClipRegion);
+            serialization = allocator.create<ClipRegion>(mClipRegion);
+            serialization->rect.set(mClipRegion.getBounds());
             break;
         }
+        mLastSerialization = serialization;
     }
     return mLastSerialization;
 }
diff --git a/libs/hwui/FrameStatsObserver.h b/libs/hwui/FrameMetricsObserver.h
similarity index 86%
rename from libs/hwui/FrameStatsObserver.h
rename to libs/hwui/FrameMetricsObserver.h
index 7abc9f1..2b42a80 100644
--- a/libs/hwui/FrameStatsObserver.h
+++ b/libs/hwui/FrameMetricsObserver.h
@@ -23,9 +23,9 @@
 namespace android {
 namespace uirenderer {
 
-class FrameStatsObserver : public VirtualLightRefBase {
+class FrameMetricsObserver : public VirtualLightRefBase {
 public:
-    virtual void notify(BufferPool::Buffer* buffer);
+    virtual void notify(BufferPool::Buffer* buffer, int dropCount);
 };
 
 }; // namespace uirenderer
diff --git a/libs/hwui/FrameStatsReporter.h b/libs/hwui/FrameMetricsReporter.h
similarity index 83%
rename from libs/hwui/FrameStatsReporter.h
rename to libs/hwui/FrameMetricsReporter.h
index b8a9432..0831d24 100644
--- a/libs/hwui/FrameStatsReporter.h
+++ b/libs/hwui/FrameMetricsReporter.h
@@ -21,7 +21,7 @@
 
 #include "BufferPool.h"
 #include "FrameInfo.h"
-#include "FrameStatsObserver.h"
+#include "FrameMetricsObserver.h"
 
 #include <string.h>
 #include <vector>
@@ -29,18 +29,18 @@
 namespace android {
 namespace uirenderer {
 
-class FrameStatsReporter {
+class FrameMetricsReporter {
 public:
-    FrameStatsReporter() {
+    FrameMetricsReporter() {
         mBufferPool = new BufferPool(kBufferSize, kBufferCount);
         LOG_ALWAYS_FATAL_IF(mBufferPool.get() == nullptr, "OOM: unable to allocate buffer pool");
     }
 
-    void addObserver(FrameStatsObserver* observer) {
+    void addObserver(FrameMetricsObserver* observer) {
         mObservers.push_back(observer);
     }
 
-    bool removeObserver(FrameStatsObserver* observer) {
+    bool removeObserver(FrameMetricsObserver* observer) {
         for (size_t i = 0; i < mObservers.size(); i++) {
             if (mObservers[i].get() == observer) {
                 mObservers.erase(mObservers.begin() + i);
@@ -54,7 +54,7 @@
         return mObservers.size() > 0;
     }
 
-    void reportFrameStats(const int64_t* stats) {
+    void reportFrameMetrics(const int64_t* stats) {
         BufferPool::Buffer* statsBuffer = mBufferPool->acquire();
 
         if (statsBuffer != nullptr) {
@@ -63,11 +63,12 @@
 
             // notify on requested threads
             for (size_t i = 0; i < mObservers.size(); i++) {
-                mObservers[i]->notify(statsBuffer);
+                mObservers[i]->notify(statsBuffer, mDroppedReports);
             }
 
             // drop our reference
             statsBuffer->release();
+            mDroppedReports = 0;
         } else {
             mDroppedReports++;
         }
@@ -79,7 +80,7 @@
     static const size_t kBufferCount = 3;
     static const size_t kBufferSize = static_cast<size_t>(FrameInfoIndex::NumIndexes);
 
-    std::vector< sp<FrameStatsObserver> > mObservers;
+    std::vector< sp<FrameMetricsObserver> > mObservers;
 
     sp<BufferPool> mBufferPool;
 
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index ea702c0..4f528b1 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -507,8 +507,8 @@
 
     mJankTracker.addFrame(*mCurrentFrameInfo);
     mRenderThread.jankTracker().addFrame(*mCurrentFrameInfo);
-    if (CC_UNLIKELY(mFrameStatsReporter.get() != nullptr)) {
-        mFrameStatsReporter->reportFrameStats(mCurrentFrameInfo->data());
+    if (CC_UNLIKELY(mFrameMetricsReporter.get() != nullptr)) {
+        mFrameMetricsReporter->reportFrameMetrics(mCurrentFrameInfo->data());
     }
 
     GpuMemoryTracker::onFrameCompleted();
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 168166e..1f81970 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -20,7 +20,7 @@
 #include "DamageAccumulator.h"
 #include "FrameInfo.h"
 #include "FrameInfoVisualizer.h"
-#include "FrameStatsReporter.h"
+#include "FrameMetricsReporter.h"
 #include "IContextFactory.h"
 #include "LayerUpdateQueue.h"
 #include "RenderNode.h"
@@ -142,26 +142,26 @@
         return mRenderThread.renderState();
     }
 
-    void addFrameStatsObserver(FrameStatsObserver* observer) {
-        if (mFrameStatsReporter.get() == nullptr) {
-            mFrameStatsReporter.reset(new FrameStatsReporter());
+    void addFrameMetricsObserver(FrameMetricsObserver* observer) {
+        if (mFrameMetricsReporter.get() == nullptr) {
+            mFrameMetricsReporter.reset(new FrameMetricsReporter());
         }
 
-        mFrameStatsReporter->addObserver(observer);
+        mFrameMetricsReporter->addObserver(observer);
     }
 
-    void removeFrameStatsObserver(FrameStatsObserver* observer) {
-        if (mFrameStatsReporter.get() != nullptr) {
-            mFrameStatsReporter->removeObserver(observer);
-            if (!mFrameStatsReporter->hasObservers()) {
-                mFrameStatsReporter.reset(nullptr);
+    void removeFrameMetricsObserver(FrameMetricsObserver* observer) {
+        if (mFrameMetricsReporter.get() != nullptr) {
+            mFrameMetricsReporter->removeObserver(observer);
+            if (!mFrameMetricsReporter->hasObservers()) {
+                mFrameMetricsReporter.reset(nullptr);
             }
         }
     }
 
     long getDroppedFrameReportCount() {
-        if (mFrameStatsReporter.get() != nullptr) {
-            return mFrameStatsReporter->getDroppedReports();
+        if (mFrameMetricsReporter.get() != nullptr) {
+            return mFrameMetricsReporter->getDroppedReports();
         }
 
         return 0;
@@ -215,7 +215,7 @@
     std::string mName;
     JankTracker mJankTracker;
     FrameInfoVisualizer mProfiler;
-    std::unique_ptr<FrameStatsReporter> mFrameStatsReporter;
+    std::unique_ptr<FrameMetricsReporter> mFrameMetricsReporter;
 
     std::set<RenderNode*> mPrefetechedLayers;
 
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 7c6cd7e..04223a7 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -568,17 +568,17 @@
     post(task);
 }
 
-CREATE_BRIDGE2(addFrameStatsObserver, CanvasContext* context,
-        FrameStatsObserver* frameStatsObserver) {
-   args->context->addFrameStatsObserver(args->frameStatsObserver);
+CREATE_BRIDGE2(addFrameMetricsObserver, CanvasContext* context,
+        FrameMetricsObserver* frameStatsObserver) {
+   args->context->addFrameMetricsObserver(args->frameStatsObserver);
    if (args->frameStatsObserver != nullptr) {
        args->frameStatsObserver->decStrong(args->context);
    }
    return nullptr;
 }
 
-void RenderProxy::addFrameStatsObserver(FrameStatsObserver* observer) {
-    SETUP_TASK(addFrameStatsObserver);
+void RenderProxy::addFrameMetricsObserver(FrameMetricsObserver* observer) {
+    SETUP_TASK(addFrameMetricsObserver);
     args->context = mContext;
     args->frameStatsObserver = observer;
     if (observer != nullptr) {
@@ -587,17 +587,17 @@
     post(task);
 }
 
-CREATE_BRIDGE2(removeFrameStatsObserver, CanvasContext* context,
-        FrameStatsObserver* frameStatsObserver) {
-   args->context->removeFrameStatsObserver(args->frameStatsObserver);
+CREATE_BRIDGE2(removeFrameMetricsObserver, CanvasContext* context,
+        FrameMetricsObserver* frameStatsObserver) {
+   args->context->removeFrameMetricsObserver(args->frameStatsObserver);
    if (args->frameStatsObserver != nullptr) {
        args->frameStatsObserver->decStrong(args->context);
    }
    return nullptr;
 }
 
-void RenderProxy::removeFrameStatsObserver(FrameStatsObserver* observer) {
-    SETUP_TASK(removeFrameStatsObserver);
+void RenderProxy::removeFrameMetricsObserver(FrameMetricsObserver* observer) {
+    SETUP_TASK(removeFrameMetricsObserver);
     args->context = mContext;
     args->frameStatsObserver = observer;
     if (observer != nullptr) {
@@ -606,16 +606,6 @@
     post(task);
 }
 
-CREATE_BRIDGE1(getDroppedFrameReportCount, CanvasContext* context) {
-    return (void*) args->context->getDroppedFrameReportCount();
-}
-
-long RenderProxy::getDroppedFrameReportCount() {
-    SETUP_TASK(getDroppedFrameReportCount);
-    args->context = mContext;
-    return (long) postAndWait(task);
-}
-
 void RenderProxy::post(RenderTask* task) {
     mRenderThread.queue(task);
 }
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 178724a..8d65a82 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -29,7 +29,7 @@
 #include <utils/StrongPointer.h>
 
 #include "../Caches.h"
-#include "../FrameStatsObserver.h"
+#include "../FrameMetricsObserver.h"
 #include "../IContextFactory.h"
 #include "CanvasContext.h"
 #include "DrawFrameTask.h"
@@ -113,8 +113,8 @@
     ANDROID_API void drawRenderNode(RenderNode* node);
     ANDROID_API void setContentDrawBounds(int left, int top, int right, int bottom);
 
-    ANDROID_API void addFrameStatsObserver(FrameStatsObserver* observer);
-    ANDROID_API void removeFrameStatsObserver(FrameStatsObserver* observer);
+    ANDROID_API void addFrameMetricsObserver(FrameMetricsObserver* observer);
+    ANDROID_API void removeFrameMetricsObserver(FrameMetricsObserver* observer);
     ANDROID_API long getDroppedFrameReportCount();
 
 private:
diff --git a/libs/hwui/tests/unit/ClipAreaTests.cpp b/libs/hwui/tests/unit/ClipAreaTests.cpp
index 4cae737..679569e 100644
--- a/libs/hwui/tests/unit/ClipAreaTests.cpp
+++ b/libs/hwui/tests/unit/ClipAreaTests.cpp
@@ -133,7 +133,7 @@
         ASSERT_NE(nullptr, serializedClip);
         ASSERT_EQ(ClipMode::Rectangle, serializedClip->mode);
         auto clipRect = reinterpret_cast<const ClipRect*>(serializedClip);
-        ASSERT_EQ(Rect(200, 200), clipRect->rect);
+        EXPECT_EQ(Rect(200, 200), clipRect->rect);
         EXPECT_EQ(serializedClip, area.serializeClip(allocator))
                 << "Requery of clip on unmodified ClipArea must return same pointer.";
     }
@@ -147,7 +147,10 @@
         ASSERT_NE(nullptr, serializedClip);
         ASSERT_EQ(ClipMode::RectangleList, serializedClip->mode);
         auto clipRectList = reinterpret_cast<const ClipRectList*>(serializedClip);
-        ASSERT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
+        EXPECT_EQ(2, clipRectList->rectList.getTransformedRectanglesCount());
+        EXPECT_FALSE(clipRectList->rect.isEmpty());
+        EXPECT_FLOAT_EQ(199.87817f, clipRectList->rect.right)
+            << "Right side should be clipped by rotated rect";
         EXPECT_EQ(serializedClip, area.serializeClip(allocator))
                 << "Requery of clip on unmodified ClipArea must return same pointer.";
     }
@@ -161,8 +164,9 @@
         ASSERT_NE(nullptr, serializedClip);
         ASSERT_EQ(ClipMode::Region, serializedClip->mode);
         auto clipRegion = reinterpret_cast<const ClipRegion*>(serializedClip);
-        ASSERT_EQ(SkIRect::MakeWH(200, 200), clipRegion->region.getBounds())
+        EXPECT_EQ(SkIRect::MakeWH(200, 200), clipRegion->region.getBounds())
                 << "Clip region should be 200x200";
+        EXPECT_EQ(Rect(200, 200), clipRegion->rect);
         EXPECT_EQ(serializedClip, area.serializeClip(allocator))
                 << "Requery of clip on unmodified ClipArea must return same pointer.";
     }
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index 800b914..e342385 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -1014,9 +1014,12 @@
      * Reads audio data from the audio hardware for recording into a byte array.
      * The format specified in the AudioRecord constructor should be
      * {@link AudioFormat#ENCODING_PCM_8BIT} to correspond to the data in the array.
+     * The format can be {@link AudioFormat#ENCODING_PCM_16BIT}, but this is deprecated.
      * @param audioData the array to which the recorded audio data is written.
-     * @param offsetInBytes index in audioData from which the data is written expressed in bytes.
+     * @param offsetInBytes index in audioData to which the data is written expressed in bytes.
+     *        Must not be negative, or cause the data access to go out of bounds of the array.
      * @param sizeInBytes the number of requested bytes.
+     *        Must not be negative, or cause the data access to go out of bounds of the array.
      * @param readMode one of {@link #READ_BLOCKING}, {@link #READ_NON_BLOCKING}.
      *     <br>With {@link #READ_BLOCKING}, the read will block until all the requested data
      *     is read.
@@ -1025,7 +1028,8 @@
      * @return the number of bytes that were read or {@link #ERROR_INVALID_OPERATION}
      *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
      *    the parameters don't resolve to valid data and indexes.
-     *    The number of bytes will not exceed sizeInBytes.
+     *    The number of bytes will be a multiple of the frame size in bytes
+     *    not to exceed sizeInBytes.
      */
     public int read(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes,
             @ReadMode int readMode) {
@@ -1053,12 +1057,14 @@
      * The format specified in the AudioRecord constructor should be
      * {@link AudioFormat#ENCODING_PCM_16BIT} to correspond to the data in the array.
      * @param audioData the array to which the recorded audio data is written.
-     * @param offsetInShorts index in audioData from which the data is written expressed in shorts.
+     * @param offsetInShorts index in audioData to which the data is written expressed in shorts.
+     *        Must not be negative, or cause the data access to go out of bounds of the array.
      * @param sizeInShorts the number of requested shorts.
+     *        Must not be negative, or cause the data access to go out of bounds of the array.
      * @return the number of shorts that were read or {@link #ERROR_INVALID_OPERATION}
      *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
      *    the parameters don't resolve to valid data and indexes.
-     *    The number of shorts will not exceed sizeInShorts.
+     *    The number of shorts will be a multiple of the channel count not to exceed sizeInShorts.
      */
     public int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts) {
         return read(audioData, offsetInShorts, sizeInShorts, READ_BLOCKING);
@@ -1070,7 +1076,9 @@
      * {@link AudioFormat#ENCODING_PCM_16BIT} to correspond to the data in the array.
      * @param audioData the array to which the recorded audio data is written.
      * @param offsetInShorts index in audioData from which the data is written expressed in shorts.
+     *        Must not be negative, or cause the data access to go out of bounds of the array.
      * @param sizeInShorts the number of requested shorts.
+     *        Must not be negative, or cause the data access to go out of bounds of the array.
      * @param readMode one of {@link #READ_BLOCKING}, {@link #READ_NON_BLOCKING}.
      *     <br>With {@link #READ_BLOCKING}, the read will block until all the requested data
      *     is read.
@@ -1079,7 +1087,7 @@
      * @return the number of shorts that were read or {@link #ERROR_INVALID_OPERATION}
      *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
      *    the parameters don't resolve to valid data and indexes.
-     *    The number of shorts will not exceed sizeInShorts.
+     *    The number of shorts will be a multiple of the channel count not to exceed sizeInShorts.
      */
     public int read(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts,
             @ReadMode int readMode) {
@@ -1108,7 +1116,9 @@
      * {@link AudioFormat#ENCODING_PCM_FLOAT} to correspond to the data in the array.
      * @param audioData the array to which the recorded audio data is written.
      * @param offsetInFloats index in audioData from which the data is written.
+     *        Must not be negative, or cause the data access to go out of bounds of the array.
      * @param sizeInFloats the number of requested floats.
+     *        Must not be negative, or cause the data access to go out of bounds of the array.
      * @param readMode one of {@link #READ_BLOCKING}, {@link #READ_NON_BLOCKING}.
      *     <br>With {@link #READ_BLOCKING}, the read will block until all the requested data
      *     is read.
@@ -1117,7 +1127,7 @@
      * @return the number of floats that were read or {@link #ERROR_INVALID_OPERATION}
      *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
      *    the parameters don't resolve to valid data and indexes.
-     *    The number of floats will not exceed sizeInFloats.
+     *    The number of floats will be a multiple of the channel count not to exceed sizeInFloats.
      */
     public int read(@NonNull float[] audioData, int offsetInFloats, int sizeInFloats,
             @ReadMode int readMode) {
@@ -1154,6 +1164,7 @@
      * The representation of the data in the buffer will depend on the format specified in
      * the AudioRecord constructor, and will be native endian.
      * @param audioBuffer the direct buffer to which the recorded audio data is written.
+     * Data is written to audioBuffer.position().
      * @param sizeInBytes the number of requested bytes. It is recommended but not enforced
      *    that the number of bytes requested be a multiple of the frame size (sample size in
      *    bytes multiplied by the channel count).
@@ -1161,7 +1172,7 @@
      *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
      *    the parameters don't resolve to valid data and indexes.
      *    The number of bytes will not exceed sizeInBytes.
-     *    The number of bytes read will truncated to be a multiple of the frame size.
+     *    The number of bytes read will be truncated to be a multiple of the frame size.
      */
     public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes) {
         return read(audioBuffer, sizeInBytes, READ_BLOCKING);
@@ -1175,6 +1186,7 @@
      * The representation of the data in the buffer will depend on the format specified in
      * the AudioRecord constructor, and will be native endian.
      * @param audioBuffer the direct buffer to which the recorded audio data is written.
+     * Data is written to audioBuffer.position().
      * @param sizeInBytes the number of requested bytes. It is recommended but not enforced
      *    that the number of bytes requested be a multiple of the frame size (sample size in
      *    bytes multiplied by the channel count).
@@ -1187,7 +1199,7 @@
      *    if the object wasn't properly initialized, or {@link #ERROR_BAD_VALUE} if
      *    the parameters don't resolve to valid data and indexes.
      *    The number of bytes will not exceed sizeInBytes.
-     *    The number of bytes read will truncated to be a multiple of the frame size.
+     *    The number of bytes read will be truncated to be a multiple of the frame size.
      */
     public int read(@NonNull ByteBuffer audioBuffer, int sizeInBytes, @ReadMode int readMode) {
         if (mState != STATE_INITIALIZED) {
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index b26b310..bdf6d9f 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1774,6 +1774,7 @@
      * or copies audio data for later playback (static buffer mode).
      * The format specified in the AudioTrack constructor should be
      * {@link AudioFormat#ENCODING_PCM_8BIT} to correspond to the data in the array.
+     * The format can be {@link AudioFormat#ENCODING_PCM_16BIT}, but this is deprecated.
      * <p>
      * In streaming mode, the write will normally block until all the data has been enqueued for
      * playback, and will return a full transfer count.  However, if the track is stopped or paused
@@ -1786,7 +1787,9 @@
      * @param audioData the array that holds the data to play.
      * @param offsetInBytes the offset expressed in bytes in audioData where the data to write
      *    starts.
+     *    Must not be negative, or cause the data access to go out of bounds of the array.
      * @param sizeInBytes the number of bytes to write in audioData after the offset.
+     *    Must not be negative, or cause the data access to go out of bounds of the array.
      * @return zero or the positive number of bytes that were written, or
      *    {@link #ERROR_INVALID_OPERATION}
      *    if the track isn't properly initialized, or {@link #ERROR_BAD_VALUE} if
@@ -1795,6 +1798,8 @@
      *    needs to be recreated.
      *    The dead object error code is not returned if some data was successfully transferred.
      *    In this case, the error is returned at the next write().
+     *    The number of bytes will be a multiple of the frame size in bytes
+     *    not to exceed sizeInBytes.
      *
      * This is equivalent to {@link #write(byte[], int, int, int)} with <code>writeMode</code>
      * set to  {@link #WRITE_BLOCKING}.
@@ -1808,6 +1813,7 @@
      * or copies audio data for later playback (static buffer mode).
      * The format specified in the AudioTrack constructor should be
      * {@link AudioFormat#ENCODING_PCM_8BIT} to correspond to the data in the array.
+     * The format can be {@link AudioFormat#ENCODING_PCM_16BIT}, but this is deprecated.
      * <p>
      * In streaming mode, the blocking behavior depends on the write mode.  If the write mode is
      * {@link #WRITE_BLOCKING}, the write will normally block until all the data has been enqueued
@@ -1823,7 +1829,9 @@
      * @param audioData the array that holds the data to play.
      * @param offsetInBytes the offset expressed in bytes in audioData where the data to write
      *    starts.
+     *    Must not be negative, or cause the data access to go out of bounds of the array.
      * @param sizeInBytes the number of bytes to write in audioData after the offset.
+     *    Must not be negative, or cause the data access to go out of bounds of the array.
      * @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
      *     effect in static mode.
      *     <br>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
@@ -1838,6 +1846,8 @@
      *    needs to be recreated.
      *    The dead object error code is not returned if some data was successfully transferred.
      *    In this case, the error is returned at the next write().
+     *    The number of bytes will be a multiple of the frame size in bytes
+     *    not to exceed sizeInBytes.
      */
     public int write(@NonNull byte[] audioData, int offsetInBytes, int sizeInBytes,
             @WriteMode int writeMode) {
@@ -1887,7 +1897,9 @@
      * @param audioData the array that holds the data to play.
      * @param offsetInShorts the offset expressed in shorts in audioData where the data to play
      *     starts.
+     *    Must not be negative, or cause the data access to go out of bounds of the array.
      * @param sizeInShorts the number of shorts to read in audioData after the offset.
+     *    Must not be negative, or cause the data access to go out of bounds of the array.
      * @return zero or the positive number of shorts that were written, or
      *    {@link #ERROR_INVALID_OPERATION}
      *    if the track isn't properly initialized, or {@link #ERROR_BAD_VALUE} if
@@ -1896,6 +1908,7 @@
      *    needs to be recreated.
      *    The dead object error code is not returned if some data was successfully transferred.
      *    In this case, the error is returned at the next write().
+     *    The number of shorts will be a multiple of the channel count not to exceed sizeInShorts.
      *
      * This is equivalent to {@link #write(short[], int, int, int)} with <code>writeMode</code>
      * set to  {@link #WRITE_BLOCKING}.
@@ -1923,7 +1936,9 @@
      * @param audioData the array that holds the data to write.
      * @param offsetInShorts the offset expressed in shorts in audioData where the data to write
      *     starts.
+     *    Must not be negative, or cause the data access to go out of bounds of the array.
      * @param sizeInShorts the number of shorts to read in audioData after the offset.
+     *    Must not be negative, or cause the data access to go out of bounds of the array.
      * @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
      *     effect in static mode.
      *     <br>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
@@ -1938,6 +1953,7 @@
      *    needs to be recreated.
      *    The dead object error code is not returned if some data was successfully transferred.
      *    In this case, the error is returned at the next write().
+     *    The number of shorts will be a multiple of the channel count not to exceed sizeInShorts.
      */
     public int write(@NonNull short[] audioData, int offsetInShorts, int sizeInShorts,
             @WriteMode int writeMode) {
@@ -1999,7 +2015,9 @@
      *     to provide samples values within the nominal range.
      * @param offsetInFloats the offset, expressed as a number of floats,
      *     in audioData where the data to write starts.
+     *    Must not be negative, or cause the data access to go out of bounds of the array.
      * @param sizeInFloats the number of floats to write in audioData after the offset.
+     *    Must not be negative, or cause the data access to go out of bounds of the array.
      * @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
      *     effect in static mode.
      *     <br>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
@@ -2014,6 +2032,7 @@
      *    needs to be recreated.
      *    The dead object error code is not returned if some data was successfully transferred.
      *    In this case, the error is returned at the next write().
+     *    The number of floats will be a multiple of the channel count not to exceed sizeInFloats.
      */
     public int write(@NonNull float[] audioData, int offsetInFloats, int sizeInFloats,
             @WriteMode int writeMode) {
@@ -2075,7 +2094,9 @@
      *     <BR>Note that upon return, the buffer position (<code>audioData.position()</code>) will
      *     have been advanced to reflect the amount of data that was successfully written to
      *     the AudioTrack.
-     * @param sizeInBytes number of bytes to write.
+     * @param sizeInBytes number of bytes to write.  It is recommended but not enforced
+     *     that the number of bytes requested be a multiple of the frame size (sample size in
+     *     bytes multiplied by the channel count).
      *     <BR>Note this may differ from <code>audioData.remaining()</code>, but cannot exceed it.
      * @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}. It has no
      *     effect in static mode.
@@ -2142,7 +2163,9 @@
      *     <BR>Note that upon return, the buffer position (<code>audioData.position()</code>) will
      *     have been advanced to reflect the amount of data that was successfully written to
      *     the AudioTrack.
-     * @param sizeInBytes number of bytes to write.
+     * @param sizeInBytes number of bytes to write.  It is recommended but not enforced
+     *     that the number of bytes requested be a multiple of the frame size (sample size in
+     *     bytes multiplied by the channel count).
      *     <BR>Note this may differ from <code>audioData.remaining()</code>, but cannot exceed it.
      * @param writeMode one of {@link #WRITE_BLOCKING}, {@link #WRITE_NON_BLOCKING}.
      *     <BR>With {@link #WRITE_BLOCKING}, the write will block until all data has been written
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index d189333..12a220f 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -88,7 +88,7 @@
 
     // For the recording session
     void connect(in IBinder sessionToken, in Uri channelUri, in Bundle params, int userId);
-    void startRecording(in IBinder sessionToken, int userId);
+    void startRecording(in IBinder sessionToken, in Uri programHint, int userId);
     void stopRecording(in IBinder sessionToken, int userId);
 
     // For TV input hardware binding
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 408a762..07781bc 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -58,6 +58,6 @@
     // For the recording session
     void connect(in Uri channelUri, in Bundle params);
     void disconnect();
-    void startRecording();
+    void startRecording(in Uri programHint);
     void stopRecording();
 }
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index 4ac5876..b15acef 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -16,6 +16,7 @@
 
 package android.media.tv;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.graphics.Rect;
 import android.media.PlaybackParams;
@@ -220,7 +221,7 @@
                 break;
             }
             case DO_START_RECORDING: {
-                mTvInputRecordingSessionImpl.startRecording();
+                mTvInputRecordingSessionImpl.startRecording((Uri) msg.obj);
                 break;
             }
             case DO_STOP_RECORDING: {
@@ -366,8 +367,8 @@
     }
 
     @Override
-    public void startRecording() {
-        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_START_RECORDING));
+    public void startRecording(@Nullable Uri programHint) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_START_RECORDING, programHint));
     }
 
     @Override
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 1cd1958..d76408e 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -2039,22 +2039,25 @@
         }
 
         /**
-         * Starts TV program recording for the current recording session.
+         * Starts TV program recording in the current recording session.
+         *
+         * @param programHint The URI for the TV program to record as a hint, built by
+         *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
          */
-        void startRecording() {
+        void startRecording(@Nullable Uri programHint) {
             if (mToken == null) {
                 Log.w(TAG, "The session has been already released");
                 return;
             }
             try {
-                mService.startRecording(mToken, mUserId);
+                mService.startRecording(mToken, programHint, mUserId);
             } catch (RemoteException e) {
                 throw new RuntimeException(e);
             }
         }
 
         /**
-         * Stops TV program recording for the current recording session.
+         * Stops TV program recording in the current recording session.
          */
         void stopRecording() {
             if (mToken == null) {
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index d48b2c8..91f1ee9 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -1699,16 +1699,26 @@
          * Called when the application requests to start recording. Recording must start
          * immediately.
          *
+         * <p>The application may supply the URI for a TV program as a hint for filling in program
+         * specific data fields in the {@link android.media.tv.TvContract.RecordedPrograms} table.
+         * A non-null {@code programHint} implies the started recording should be of that specific
+         * program, whereas null {@code programHint} does not impose such a requirement and the
+         * recording can span across multiple TV programs. In either case, the application must call
+         * {@link TvRecordingClient#stopRecording()} to stop the recording.
+         *
          * <p>The session must call either {@link #notifyRecordingStarted()} or
-         * {@link #notifyError(int)}}.
+         * {@link #notifyError(int)}.
+         *
+         * @param programHint The URI for the TV program to record as a hint, built by
+         *            {@link TvContract#buildProgramUri(long)}. Can be {@code null}.
          */
-        public abstract void onStartRecording();
+        public abstract void onStartRecording(@Nullable Uri programHint);
 
         /**
          * Called when the application requests to stop recording. Recording must stop immediately.
          *
          * <p>The session must call either {@link #notifyRecordingStopped(Uri)} or
-         * {@link #notifyError(int)}}.
+         * {@link #notifyError(int)}.
          */
         public abstract void onStopRecording();
 
@@ -1744,11 +1754,11 @@
         }
 
         /**
-         * Calls {@link #onStartRecording()}.
+         * Calls {@link #onStartRecording(Uri)}.
          *
          */
-        void startRecording() {
-            onStartRecording();
+        void startRecording(@Nullable  Uri programHint) {
+            onStartRecording(programHint);
         }
 
         /**
diff --git a/media/java/android/media/tv/TvRecordingClient.java b/media/java/android/media/tv/TvRecordingClient.java
index 1d80068..b943c3d 100644
--- a/media/java/android/media/tv/TvRecordingClient.java
+++ b/media/java/android/media/tv/TvRecordingClient.java
@@ -17,6 +17,7 @@
 package android.media.tv;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.net.Uri;
@@ -131,20 +132,31 @@
     }
 
     /**
-     * Starts TV program recording for the current recording session. It is expected that recording
+     * Starts TV program recording in the current recording session. It is expected that recording
      * starts immediately after calling this method.
      *
+     * <p>The application may supply the URI for a TV program as a hint to the corresponding TV
+     * input service for filling in program specific data fields in the
+     * {@link android.media.tv.TvContract.RecordedPrograms} table. A non-null {@code programHint}
+     * implies the started recording should be of that specific program, whereas null
+     * {@code programHint} does not impose such a requirement and the recording can span across
+     * multiple TV programs. In either case, the caller must call {@link #stopRecording()} to stop
+     * the recording.
+     *
      * <p>The recording session will respond by calling
      * {@link RecordingCallback#onRecordingStarted()} or {@link RecordingCallback#onError(int)}.
+     *
+     * @param programHint The URI for the TV program to record as a hint, built by
+     *            {@link TvContract#buildProgramUri(long)}. Can be null.
      */
-    public void startRecording() {
+    public void startRecording(@Nullable  Uri programHint) {
         if (mSession != null) {
-            mSession.startRecording();
+            mSession.startRecording(programHint);
         }
     }
 
     /**
-     * Stops TV program recording for the current recording session. It is expected that recording
+     * Stops TV program recording in the current recording session. It is expected that recording
      * stops immediately after calling this method.
      *
      * <p>The recording session will respond by calling
@@ -325,7 +337,7 @@
         @Override
         public void onRecordingStopped(TvInputManager.Session session, Uri recordedProgramUri) {
             if (DEBUG) {
-                Log.d(TAG, "onRecordingStopped()");
+                Log.d(TAG, "onRecordingStopped(recordedProgramUri= " + recordedProgramUri + ")");
             }
             if (this != mSessionCallback) {
                 Log.w(TAG, "onRecordingStopped - session not created");
@@ -337,7 +349,7 @@
         @Override
         public void onError(TvInputManager.Session session, int error) {
             if (DEBUG) {
-                Log.d(TAG, "onError()");
+                Log.d(TAG, "onError(error=" + error + ")");
             }
             if (this != mSessionCallback) {
                 Log.w(TAG, "onError - session not created");
@@ -350,7 +362,8 @@
         public void onSessionEvent(TvInputManager.Session session, String eventType,
                 Bundle eventArgs) {
             if (DEBUG) {
-                Log.d(TAG, "onSessionEvent(" + eventType + ")");
+                Log.d(TAG, "onSessionEvent(eventType=" + eventType + ", eventArgs=" + eventArgs
+                        + ")");
             }
             if (this != mSessionCallback) {
                 Log.w(TAG, "onSessionEvent - session not created");
diff --git a/packages/DocumentsUI/res/layout/directory_cluster.xml b/packages/DocumentsUI/res/layout/directory_cluster.xml
index 2fa09d3..d84ef08 100644
--- a/packages/DocumentsUI/res/layout/directory_cluster.xml
+++ b/packages/DocumentsUI/res/layout/directory_cluster.xml
@@ -27,6 +27,7 @@
 
     <FrameLayout
         android:id="@+id/container_directory"
+        android:clipToPadding="false"
         android:layout_width="match_parent"
         android:layout_height="0dp"
         android:layout_weight="1" />
diff --git a/packages/DocumentsUI/res/layout/fragment_directory.xml b/packages/DocumentsUI/res/layout/fragment_directory.xml
index 223d729..d0364ff 100644
--- a/packages/DocumentsUI/res/layout/fragment_directory.xml
+++ b/packages/DocumentsUI/res/layout/fragment_directory.xml
@@ -17,8 +17,10 @@
 <com.android.documentsui.DirectoryView xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:animateLayoutChanges="true">
+    android:background="@color/window_background"
+    android:outlineProvider="bounds"
+    android:elevation="4dp"
+    android:orientation="vertical">
 
     <ProgressBar
         android:id="@+id/progressbar"
@@ -43,6 +45,7 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical"
+        android:background="@color/window_background"
         android:visibility="gone">
 
         <LinearLayout
@@ -81,7 +84,6 @@
 
         <android.support.v7.widget.RecyclerView
             android:id="@+id/list"
-            android:background="@color/window_background"
             android:scrollbars="vertical"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 3c21a21..73d0f72 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -219,8 +219,8 @@
     void onStackRestored(boolean restored, boolean external) {}
 
     void onRootPicked(RootInfo root) {
-        // Skip refreshing if root didn't change
-        if(root.equals(getCurrentRoot())) {
+        // Skip refreshing if root nor directory didn't change
+        if (root.equals(getCurrentRoot()) && mState.stack.size() == 1) {
             return;
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Events.java b/packages/DocumentsUI/src/com/android/documentsui/Events.java
index 99b425e..14d4e2d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Events.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Events.java
@@ -91,6 +91,8 @@
             case KeyEvent.KEYCODE_DPAD_RIGHT:
             case KeyEvent.KEYCODE_MOVE_HOME:
             case KeyEvent.KEYCODE_MOVE_END:
+            case KeyEvent.KEYCODE_PAGE_UP:
+            case KeyEvent.KEYCODE_PAGE_DOWN:
                 return true;
             default:
                 return false;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 70bee3c..b6c27b5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -53,9 +53,7 @@
 import android.support.design.widget.Snackbar;
 import android.support.v7.widget.GridLayoutManager;
 import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
-import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.LayoutManager;
 import android.support.v7.widget.RecyclerView.OnItemTouchListener;
 import android.support.v7.widget.RecyclerView.RecyclerListener;
 import android.support.v7.widget.RecyclerView.ViewHolder;
@@ -94,11 +92,13 @@
 import com.android.documentsui.Shared;
 import com.android.documentsui.Snackbars;
 import com.android.documentsui.State;
+import com.android.documentsui.State.ViewMode;
 import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.RootInfo;
 import com.android.documentsui.services.FileOperationService;
+import com.android.documentsui.services.FileOperationService.OpType;
 import com.android.documentsui.services.FileOperations;
 import com.google.common.collect.Lists;
 
@@ -155,9 +155,7 @@
     private LoaderCallbacks<DirectoryResult> mCallbacks;
     private FragmentTuner mTuner;
     private DocumentClipper mClipper;
-    // These are lazily initialized.
-    private LinearLayoutManager mListLayout;
-    private GridLayoutManager mGridLayout;
+    private GridLayoutManager mLayout;
     private int mColumnCount = 1;  // This will get updated when layout changes.
 
     private MessageBar mMessageBar;
@@ -182,22 +180,6 @@
                     }
                 });
 
-        // TODO: Rather than update columns on layout changes, push this
-        // code (or something like it) into GridLayoutManager.
-        mRecView.addOnLayoutChangeListener(
-                new View.OnLayoutChangeListener() {
-
-                    @Override
-                    public void onLayoutChange(
-                            View v, int left, int top, int right, int bottom, int oldLeft,
-                            int oldTop, int oldRight, int oldBottom) {
-                        mColumnCount = calculateColumnCount();
-                        if (mGridLayout != null) {
-                            mGridLayout.setSpanCount(mColumnCount);
-                        }
-                    }
-                });
-
         mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity()));
 
         // TODO: Add a divider between views (which might use RecyclerView.ItemDecoration).
@@ -240,6 +222,13 @@
 
         mRecView.setAdapter(mAdapter);
 
+        mLayout = new GridLayoutManager(getContext(), mColumnCount);
+        SpanSizeLookup lookup = mAdapter.createSpanSizeLookup();
+        if (lookup != null) {
+            mLayout.setSpanSizeLookup(lookup);
+        }
+        mRecView.setLayoutManager(mLayout);
+
         mGestureDetector = new ListeningGestureDetector(this.getContext(), new GestureListener());
 
         mRecView.addOnItemTouchListener(mGestureDetector);
@@ -432,41 +421,28 @@
     }
 
     /**
-     * Returns a {@code LayoutManager} for {@code mode}, lazily initializing
-     * classes as needed.
+     * Updates the layout after the view mode switches.
+     * @param mode The new view mode.
      */
-    private void updateLayout(int mode) {
-        final LayoutManager layout;
-        switch (mode) {
-            case MODE_GRID:
-                if (mGridLayout == null) {
-                    mGridLayout = new GridLayoutManager(getContext(), mColumnCount);
-                    SpanSizeLookup lookup = mAdapter.createSpanSizeLookup();
-                    if (lookup != null) {
-                        mGridLayout.setSpanSizeLookup(lookup);
-                    }
-                }
-                layout = mGridLayout;
-                break;
-            case MODE_LIST:
-                if (mListLayout == null) {
-                    mListLayout = new LinearLayoutManager(getContext());
-                }
-                layout = mListLayout;
-                break;
-            default:
-                throw new IllegalArgumentException("Unsupported layout mode: " + mode);
+    private void updateLayout(@ViewMode int mode) {
+        mColumnCount = calculateColumnCount(mode);
+        if (mLayout != null) {
+            mLayout.setSpanCount(mColumnCount);
         }
 
         int pad = getDirectoryPadding(mode);
         mRecView.setPadding(pad, pad, pad, pad);
-        // setting layout manager automatically invalidates existing ViewHolders.
-        mRecView.setLayoutManager(layout);
+        mRecView.requestLayout();
         mSelectionManager.handleLayoutChanged();  // RecyclerView doesn't do this for us
         mIconHelper.setViewMode(mode);
     }
 
-    private int calculateColumnCount() {
+    private int calculateColumnCount(@ViewMode int mode) {
+        if (mode == MODE_LIST) {
+            // List mode is a "grid" with 1 column.
+            return 1;
+        }
+
         int cellWidth = getResources().getDimensionPixelSize(R.dimen.grid_width);
         int cellMargin = 2 * getResources().getDimensionPixelSize(R.dimen.grid_item_margin);
         int viewPadding = mRecView.getPaddingLeft() + mRecView.getPaddingRight();
@@ -478,14 +454,12 @@
         return columnCount;
     }
 
-    private int getDirectoryPadding(int mode) {
+    private int getDirectoryPadding(@ViewMode int mode) {
         switch (mode) {
             case MODE_GRID:
-                return getResources().getDimensionPixelSize(
-                        R.dimen.grid_container_padding);
+                return getResources().getDimensionPixelSize(R.dimen.grid_container_padding);
             case MODE_LIST:
-                return getResources().getDimensionPixelSize(
-                        R.dimen.list_container_padding);
+                return getResources().getDimensionPixelSize(R.dimen.list_container_padding);
             default:
                 throw new IllegalArgumentException("Unsupported layout mode: " + mode);
         }
@@ -784,7 +758,7 @@
                 .show();
     }
 
-    private void transferDocuments(final Selection selected, final int mode) {
+    private void transferDocuments(final Selection selected, final @OpType int mode) {
         // Pop up a dialog to pick a destination.  This is inadequate but works for now.
         // TODO: Implement a picker that is to spec.
         final Intent intent = new Intent(
@@ -876,17 +850,8 @@
         msgView.setText(msg);
         imageView.setImageResource(drawable);
 
-        content.animate().cancel();  // cancel any ongoing animations
-
-        content.setAlpha(0);
         mEmptyView.setVisibility(View.VISIBLE);
         mRecView.setVisibility(View.GONE);
-
-        // fade in the content, so it looks purdy like
-        content.animate()
-                .alpha(1f)
-                .setDuration(EMPTY_REVEAL_DURATION)
-                .setListener(null);
     }
 
     private void showDirectory() {
@@ -1289,12 +1254,14 @@
          * @return The adapter position of the destination item. Could be RecyclerView.NO_POSITION.
          */
         private int findTargetPosition(View view, int keyCode) {
-            if (keyCode == KeyEvent.KEYCODE_MOVE_HOME) {
-                return 0;
-            }
-
-            if (keyCode == KeyEvent.KEYCODE_MOVE_END) {
-                return mAdapter.getItemCount() - 1;
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_MOVE_HOME:
+                    return 0;
+                case KeyEvent.KEYCODE_MOVE_END:
+                    return mAdapter.getItemCount() - 1;
+                case KeyEvent.KEYCODE_PAGE_UP:
+                case KeyEvent.KEYCODE_PAGE_DOWN:
+                    return findTargetPositionByPage(view, keyCode);
             }
 
             // Find a navigation target based on the arrow key that the user pressed.
@@ -1330,6 +1297,50 @@
         }
 
         /**
+         * Given a PgUp/PgDn event and the current view, find the position of the target view.
+         * This returns:
+         * <li>The position of the topmost (or bottom-most) visible item, if the current item is not
+         *     the top- or bottom-most visible item.
+         * <li>The position of an item that is one page's worth of items up (or down) if the current
+         *      item is the top- or bottom-most visible item.
+         * <li>The first (or last) item, if paging up (or down) would go past those limits.
+         * @param view The view that received the key event.
+         * @param keyCode Must be KEYCODE_PAGE_UP or KEYCODE_PAGE_DOWN.
+         * @return The adapter position of the target item.
+         */
+        private int findTargetPositionByPage(View view, int keyCode) {
+            int first = mLayout.findFirstVisibleItemPosition();
+            int last = mLayout.findLastVisibleItemPosition();
+            int current = mRecView.getChildAdapterPosition(view);
+            int pageSize = last - first + 1;
+
+            if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
+                if (current > first) {
+                    // If the current item isn't the first item, target the first item.
+                    return first;
+                } else {
+                    // If the current item is the first item, target the item one page up.
+                    int target = current - pageSize;
+                    return target < 0 ? 0 : target;
+                }
+            }
+
+            if (keyCode == KeyEvent.KEYCODE_PAGE_DOWN) {
+                if (current < last) {
+                    // If the current item isn't the last item, target the last item.
+                    return last;
+                } else {
+                    // If the current item is the last item, target the item one page down.
+                    int target = current + pageSize;
+                    int max = mAdapter.getItemCount() - 1;
+                    return target < max ? target : max;
+                }
+            }
+
+            throw new IllegalArgumentException("Unsupported keyCode: " + keyCode);
+        }
+
+        /**
          * Requests focus for the item in the given adapter position, scrolling the RecyclerView if
          * necessary.
          *
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java
index 43c2f63..0930c22 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java
@@ -27,7 +27,6 @@
 
 import com.android.documentsui.State;
 
-import java.nio.channels.UnsupportedAddressTypeException;
 import java.util.List;
 
 /**
@@ -87,7 +86,7 @@
      * we adjust sizes.
      */
     GridLayoutManager.SpanSizeLookup createSpanSizeLookup() {
-        throw new UnsupportedAddressTypeException();
+        throw new UnsupportedOperationException();
     }
 
     static boolean isDirectory(Cursor cursor) {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/ActivityTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/ActivityTest.java
new file mode 100644
index 0000000..34f1120
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/ActivityTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.documentsui;
+
+import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+import static com.android.documentsui.StubProvider.ROOT_1_ID;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.provider.DocumentsContract.Document;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.MotionEvent;
+
+import com.android.documentsui.model.RootInfo;
+
+/**
+ * Provides basic test environment for UI tests:
+ * - Launches activity
+ * - Creates and gives access to test root directories and test files
+ * - Cleans up the test environment
+ */
+public abstract class ActivityTest<T extends Activity> extends ActivityInstrumentationTestCase2<T> {
+
+    static final int TIMEOUT = 5000;
+
+    // Testing files. For custom ones, override initTestFiles().
+    public static final String dirName1 = "Dir1";
+    public static final String fileName1 = "file1.log";
+    public static final String fileName2 = "file12.png";
+    public static final String fileName3 = "anotherFile0.log";
+    public static final String fileName4 = "poodles.text";
+    public static final String fileNameNoRename = "NO_RENAMEfile.txt";
+
+    public UiBot bot;
+    public UiDevice device;
+    public Context context;
+
+    public RootInfo rootDir0;
+    public RootInfo rootDir1;
+
+    ContentResolver mResolver;
+    DocumentsProviderHelper mDocsHelper;
+    ContentProviderClient mClient;
+
+    public ActivityTest(Class<T> activityClass) {
+        super(activityClass);
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        device = UiDevice.getInstance(getInstrumentation());
+        // NOTE: Must be the "target" context, else security checks in content provider will fail.
+        context = getInstrumentation().getTargetContext();
+        bot = new UiBot(device, context, TIMEOUT);
+
+        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
+        bot.revealLauncher();
+
+        mResolver = context.getContentResolver();
+        mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
+        mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
+
+        rootDir0 = mDocsHelper.getRoot(ROOT_0_ID);
+        rootDir1 = mDocsHelper.getRoot(ROOT_1_ID);
+
+        launchActivity();
+
+        bot.revealApp();
+        resetStorage();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mClient.release();
+        super.tearDown();
+    }
+
+    void launchActivity() {
+        final Intent intent = context.getPackageManager().getLaunchIntentForPackage(
+                UiBot.TARGET_PKG);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        setActivityIntent(intent);
+        getActivity();  // Launch the activity.
+    }
+
+    void resetStorage() throws RemoteException {
+        mClient.call("clear", null, null);
+        device.waitForIdle();
+    }
+
+    void initTestFiles() throws RemoteException {
+        mDocsHelper.createFolder(rootDir0, dirName1);
+        mDocsHelper.createDocument(rootDir0, "text/plain", fileName1);
+        mDocsHelper.createDocument(rootDir0, "image/png", fileName2);
+        mDocsHelper.createDocumentWithFlags(rootDir0.documentId, "text/plain", fileNameNoRename,
+                Document.FLAG_SUPPORTS_WRITE);
+
+        mDocsHelper.createDocument(rootDir1, "text/plain", fileName3);
+        mDocsHelper.createDocument(rootDir1, "text/plain", fileName4);
+    }
+
+    void assertDefaultContentOfTestDir0() throws UiObjectNotFoundException {
+        bot.assertDocumentsCount(ROOT_0_ID, 4);
+        bot.assertHasDocuments(fileName1, fileName2, dirName1, fileNameNoRename);
+    }
+
+    void assertDefaultContentOfTestDir1() throws UiObjectNotFoundException {
+        bot.assertDocumentsCount(ROOT_1_ID, 2);
+        bot.assertHasDocuments(fileName3, fileName4);
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
index 5df4dca..af478ea 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
@@ -92,7 +92,8 @@
             Uri uri = DocumentsContract.createDocument(mClient, parentUri, mimeType, name);
             return uri;
         } catch (RemoteException e) {
-            throw new RuntimeException("Couldn't create document: " + name + " with mimetype " + mimeType, e);
+            throw new RuntimeException("Couldn't create document: " + name + " with mimetype "
+                    + mimeType, e);
         }
     }
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
index 243c357..737b376 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
@@ -38,122 +38,74 @@
 import com.android.documentsui.model.RootInfo;
 
 @LargeTest
-public class DownloadsActivityUiTest extends ActivityInstrumentationTestCase2<DownloadsActivity> {
+public class DownloadsActivityUiTest extends ActivityTest<DownloadsActivity> {
 
     private static final int TIMEOUT = 5000;
     private static final String TAG = "DownloadsActivityUiTest";
     private static final String TARGET_PKG = "com.android.documentsui";
     private static final String LAUNCHER_PKG = "com.android.launcher";
 
-    private UiBot mBot;
-    private UiDevice mDevice;
-    private Context mContext;
-    private ContentResolver mResolver;
-    private DocumentsProviderHelper mDocsHelper;
-    private ContentProviderClient mClient;
-    private RootInfo mRoot;
-
     public DownloadsActivityUiTest() {
         super(DownloadsActivity.class);
     }
 
-    public void setUp() throws Exception {
-        // Initialize UiDevice instance.
-        Instrumentation instrumentation = getInstrumentation();
-
-        mDevice = UiDevice.getInstance(instrumentation);
-
-        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
-
-        // Start from the home screen.
-        mDevice.pressHome();
-        mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), TIMEOUT);
-
-        // NOTE: Must be the "target" context, else security checks in content provider will fail.
-        mContext = instrumentation.getTargetContext();
-        mResolver = mContext.getContentResolver();
-
-        mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
-        mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
-
-        mRoot = mDocsHelper.getRoot(ROOT_0_ID);
-
-        // Open the Downloads activity on our stub provider root.
-        Intent intent = new Intent(DocumentsContract.ACTION_MANAGE_ROOT);
-        intent.setDataAndType(mRoot.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
+    @Override
+    void launchActivity() {
+        final Intent intent = new Intent(DocumentsContract.ACTION_MANAGE_ROOT);
+        intent.setDataAndType(rootDir0.getUri(), DocumentsContract.Root.MIME_TYPE_ITEM);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
         setActivityIntent(intent);
-        getActivity();  // Start the activity.
-
-        // Wait for the app to appear.
-        mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
-        mDevice.waitForIdle();
-
-        mBot = new UiBot(mDevice, mContext, TIMEOUT);
-
-        resetStorage();  // Just in case a test failed and tearDown didn't happen.
+        getActivity();  // Launch the activity.
     }
 
     @Override
-    protected void tearDown() throws Exception {
-        Log.d(TAG, "Resetting storage from setUp");
-        resetStorage();
-        mClient.release();
-
-        super.tearDown();
-    }
-
-    private void resetStorage() throws RemoteException {
-        mClient.call("clear", null, null);
-        // TODO: Would be nice to have an event to wait on here.
-        mDevice.waitForIdle();
-    }
-
-    private void initTestFiles() throws RemoteException {
-        mDocsHelper.createDocument(mRoot, "text/plain", "file0.log");
-        mDocsHelper.createDocument(mRoot, "image/png", "file1.png");
-        mDocsHelper.createDocument(mRoot, "text/csv", "file2.csv");
+    void initTestFiles() throws RemoteException {
+        mDocsHelper.createDocument(rootDir0, "text/plain", "file0.log");
+        mDocsHelper.createDocument(rootDir0, "image/png", "file1.png");
+        mDocsHelper.createDocument(rootDir0, "text/csv", "file2.csv");
     }
 
     public void testWindowTitle() throws Exception {
         initTestFiles();
 
-        mBot.assertWindowTitle(ROOT_0_ID);
+        bot.assertWindowTitle(ROOT_0_ID);
     }
 
     public void testFilesListed() throws Exception {
         initTestFiles();
 
-        mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
+        bot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
     }
 
     public void testFilesList_LiveUpdate() throws Exception {
         initTestFiles();
 
-        mDocsHelper.createDocument(mRoot, "yummers/sandwich", "Ham & Cheese.sandwich");
-        mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
+        mDocsHelper.createDocument(rootDir0, "yummers/sandwich", "Ham & Cheese.sandwich");
+
+        device.waitForIdle();
+        bot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
     }
 
     public void testDeleteDocument() throws Exception {
         initTestFiles();
 
-        mBot.clickDocument("file1.png");
-        mDevice.waitForIdle();
-        mBot.menuDelete().click();
+        bot.clickDocument("file1.png");
+        device.waitForIdle();
+        bot.menuDelete().click();
 
-        mBot.waitForDeleteSnackbar();
-        assertFalse(mBot.hasDocuments("file1.png"));
+        bot.waitForDeleteSnackbar();
+        assertFalse(bot.hasDocuments("file1.png"));
 
-        mBot.waitForDeleteSnackbarGone();
-        assertFalse(mBot.hasDocuments("file1.png"));
+        bot.waitForDeleteSnackbarGone();
+        assertFalse(bot.hasDocuments("file1.png"));
     }
 
     public void testSupportsShare() throws Exception {
         initTestFiles();
 
-        mBot.clickDocument("file1.png");
-        mDevice.waitForIdle();
-        assertNotNull(mBot.menuShare());
+        bot.clickDocument("file1.png");
+        device.waitForIdle();
+        assertNotNull(bot.menuShare());
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 868dbbb..7fd2416 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -30,7 +30,6 @@
 import android.support.test.uiautomator.Configurator;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
-import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 import android.view.MotionEvent;
@@ -38,96 +37,35 @@
 import com.android.documentsui.model.RootInfo;
 
 @LargeTest
-public class FilesActivityUiTest extends ActivityInstrumentationTestCase2<FilesActivity> {
+public class FilesActivityUiTest extends ActivityTest<FilesActivity> {
 
     private static final int TIMEOUT = 5000;
     private static final String TAG = "FilesActivityUiTest";
     private static final String TARGET_PKG = "com.android.documentsui";
     private static final String LAUNCHER_PKG = "com.android.launcher";
 
-    private UiBot mBot;
-    private UiDevice mDevice;
-    private Context mContext;
-    private ContentResolver mResolver;
-    private DocumentsProviderHelper mDocsHelper;
-    private ContentProviderClient mClient;
-    private RootInfo mRoot_0;
-    private RootInfo mRoot_1;
-
     public FilesActivityUiTest() {
         super(FilesActivity.class);
     }
 
-    public void setUp() throws Exception {
-        // Initialize UiDevice instance.
-        Instrumentation instrumentation = getInstrumentation();
-
-        mDevice = UiDevice.getInstance(instrumentation);
-
-        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
-
-        // Start from the home screen.
-        mDevice.pressHome();
-        mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), TIMEOUT);
-
-        // NOTE: Must be the "target" context, else security checks in content provider will fail.
-        mContext = instrumentation.getTargetContext();
-        mResolver = mContext.getContentResolver();
-
-        mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
-        assertNotNull("Failed to acquire ContentProviderClient.", mClient);
-        mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
-
-        // Launch app.
-        Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
-        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        setActivityIntent(intent);
-        getActivity();  // Start the activity.
-
-        // Wait for the app to appear.
-        mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
-        mDevice.waitForIdle();
-
-        mBot = new UiBot(mDevice, mContext, TIMEOUT);
-
-        resetStorage();  // Just incase a test failed and tearDown didn't happen.
-    }
-
     @Override
-    protected void tearDown() throws Exception {
-        Log.d(TAG, "Resetting storage from setUp");
-        resetStorage();
-        mClient.release();
+    public void initTestFiles() throws RemoteException {
+        mDocsHelper.createDocument(rootDir0, "text/plain", "file0.log");
+        mDocsHelper.createDocument(rootDir0, "image/png", "file1.png");
+        mDocsHelper.createDocument(rootDir0, "text/csv", "file2.csv");
 
-        super.tearDown();
-    }
-
-    private void resetStorage() throws RemoteException {
-        mClient.call("clear", null, null);
-        // TODO: Would be nice to have an event to wait on here.
-        mDevice.waitForIdle();
-    }
-
-    private void initTestFiles() throws RemoteException {
-        mRoot_0 = mDocsHelper.getRoot(ROOT_0_ID);
-        mRoot_1 = mDocsHelper.getRoot(ROOT_1_ID);
-
-        mDocsHelper.createDocument(mRoot_0, "text/plain", "file0.log");
-        mDocsHelper.createDocument(mRoot_0, "image/png", "file1.png");
-        mDocsHelper.createDocument(mRoot_0, "text/csv", "file2.csv");
-
-        mDocsHelper.createDocument(mRoot_1, "text/plain", "anotherFile0.log");
-        mDocsHelper.createDocument(mRoot_1, "text/plain", "poodles.text");
+        mDocsHelper.createDocument(rootDir1, "text/plain", "anotherFile0.log");
+        mDocsHelper.createDocument(rootDir1, "text/plain", "poodles.text");
     }
 
     public void testRootsListed() throws Exception {
         initTestFiles();
 
-        mBot.openRoot(ROOT_0_ID);
+        bot.openRoot(ROOT_0_ID);
 
         // Should also have Drive, but that requires pre-configuration of devices
         // We omit for now.
-        mBot.assertHasRoots(
+        bot.assertHasRoots(
                 "Images",
                 "Videos",
                 "Audio",
@@ -140,60 +78,60 @@
     public void testFilesListed() throws Exception {
         initTestFiles();
 
-        mBot.openRoot(ROOT_0_ID);
-        mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
+        bot.openRoot(ROOT_0_ID);
+        bot.assertHasDocuments("file0.log", "file1.png", "file2.csv");
     }
 
     public void testLoadsHomeByDefault() throws Exception {
         initTestFiles();
 
-        mDevice.waitForIdle();
-        mBot.assertWindowTitle("Home");
+        device.waitForIdle();
+        bot.assertWindowTitle("Home");
     }
 
     public void testRootClickSetsWindowTitle() throws Exception {
         initTestFiles();
 
-        mBot.openRoot("Downloads");
-        mBot.assertWindowTitle("Downloads");
+        bot.openRoot("Downloads");
+        bot.assertWindowTitle("Downloads");
     }
 
     public void testFilesList_LiveUpdate() throws Exception {
         initTestFiles();
 
-        mBot.openRoot(ROOT_0_ID);
-        mDocsHelper.createDocument(mRoot_0, "yummers/sandwich", "Ham & Cheese.sandwich");
+        bot.openRoot(ROOT_0_ID);
+        mDocsHelper.createDocument(rootDir0, "yummers/sandwich", "Ham & Cheese.sandwich");
 
-        mDevice.waitForIdle();
-        mBot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
+        device.waitForIdle();
+        bot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
     }
 
     public void testDeleteDocument() throws Exception {
         initTestFiles();
 
-        mBot.openRoot(ROOT_0_ID);
+        bot.openRoot(ROOT_0_ID);
 
-        mBot.clickDocument("file1.png");
-        mDevice.waitForIdle();
-        mBot.menuDelete().click();
+        bot.clickDocument("file1.png");
+        device.waitForIdle();
+        bot.menuDelete().click();
 
-        mBot.waitForDeleteSnackbar();
-        assertFalse(mBot.hasDocuments("file1.png"));
+        bot.waitForDeleteSnackbar();
+        assertFalse(bot.hasDocuments("file1.png"));
 
-        mBot.waitForDeleteSnackbarGone();
-        assertFalse(mBot.hasDocuments("file1.png"));
+        bot.waitForDeleteSnackbarGone();
+        assertFalse(bot.hasDocuments("file1.png"));
 
         // Now delete from another root.
-        mBot.openRoot(ROOT_1_ID);
+        bot.openRoot(ROOT_1_ID);
 
-        mBot.clickDocument("poodles.text");
-        mDevice.waitForIdle();
-        mBot.menuDelete().click();
+        bot.clickDocument("poodles.text");
+        device.waitForIdle();
+        bot.menuDelete().click();
 
-        mBot.waitForDeleteSnackbar();
-        assertFalse(mBot.hasDocuments("poodles.text"));
+        bot.waitForDeleteSnackbar();
+        assertFalse(bot.hasDocuments("poodles.text"));
 
-        mBot.waitForDeleteSnackbarGone();
-        assertFalse(mBot.hasDocuments("poodles.text"));
+        bot.waitForDeleteSnackbarGone();
+        assertFalse(bot.hasDocuments("poodles.text"));
     }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
index 5c6254f..303f2d1 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
@@ -17,7 +17,6 @@
 package com.android.documentsui;
 
 import static com.android.documentsui.StubProvider.ROOT_0_ID;
-import static com.android.documentsui.UiTestEnvironment.TIMEOUT;
 
 import android.support.test.uiautomator.UiObject;
 import android.support.test.uiautomator.UiObjectNotFoundException;
@@ -25,142 +24,141 @@
 import android.test.suitebuilder.annotation.LargeTest;
 
 @LargeTest
-public class RenameDocumentUiTest extends InstrumentationTestCase {
+public class RenameDocumentUiTest extends ActivityTest<FilesActivity> {
 
     private static final String TAG = "RenamDocumentUiTest";
 
     private final String newName = "kitties.log";
 
-    private UiTestEnvironment mHelper;
+    public RenameDocumentUiTest() {
+        super(FilesActivity.class);
+    }
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        mHelper = new UiTestEnvironment(getInstrumentation());
-        mHelper.launch();
-        mHelper.initTestFiles();
-        mHelper.bot().openRoot(ROOT_0_ID);
+        initTestFiles();
+        bot.openRoot(ROOT_0_ID);
     }
 
     public void testRenameEnabled_SingleSelection() throws Exception {
-        mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
-        mHelper.bot().openOverflowMenu();
-        mHelper.bot().assertMenuEnabled(R.string.menu_rename, true);
+        bot.selectDocument(fileName1);
+        bot.openOverflowMenu();
+        bot.assertMenuEnabled(R.string.menu_rename, true);
 
         // Dismiss more options window
-        mHelper.device().pressBack();
+        device.pressBack();
     }
 
     public void testNoRenameSupport_SingleSelection() throws Exception {
-        mHelper.bot().selectDocument(UiTestEnvironment.fileNameNoRename);
-        mHelper.bot().openOverflowMenu();
-        mHelper.bot().assertMenuEnabled(R.string.menu_rename, false);
+        bot.selectDocument(fileNameNoRename);
+        bot.openOverflowMenu();
+        bot.assertMenuEnabled(R.string.menu_rename, false);
 
         // Dismiss more options window
-        mHelper.device().pressBack();
+        device.pressBack();
     }
 
     public void testOneHasRenameSupport_MultipleSelection() throws Exception {
-        mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
-        mHelper.bot().selectDocument(UiTestEnvironment.fileNameNoRename);
-        mHelper.bot().openOverflowMenu();
-        mHelper.bot().assertMenuEnabled(R.string.menu_rename, false);
+        bot.selectDocument(fileName1);
+        bot.selectDocument(fileNameNoRename);
+        bot.openOverflowMenu();
+        bot.assertMenuEnabled(R.string.menu_rename, false);
 
         // Dismiss more options window
-        mHelper.device().pressBack();
+        device.pressBack();
     }
 
     public void testRenameDisabled_MultipleSelection() throws Exception {
-        mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
-        mHelper.bot().selectDocument(UiTestEnvironment.fileName2);
-        mHelper.bot().openOverflowMenu();
-        mHelper.bot().assertMenuEnabled(R.string.menu_rename, false);
+        bot.selectDocument(fileName1);
+        bot.selectDocument(fileName2);
+        bot.openOverflowMenu();
+        bot.assertMenuEnabled(R.string.menu_rename, false);
 
         // Dismiss more options window
-        mHelper.device().pressBack();
+        device.pressBack();
     }
 
     public void testRenameFile_OkButton() throws Exception {
-        mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
-        mHelper.bot().openOverflowMenu();
-        mHelper.bot().openDialog(R.string.menu_rename);
-        mHelper.bot().setDialogText(newName);
-        mHelper.bot().dismissKeyboardIfPresent();
+        bot.selectDocument(fileName1);
+        bot.openOverflowMenu();
+        bot.openDialog(R.string.menu_rename);
+        bot.setDialogText(newName);
+        bot.dismissKeyboardIfPresent();
 
-        mHelper.device().waitForIdle(TIMEOUT);
-        mHelper.bot().findRenameDialogOkButton().click();
-        mHelper.device().waitForIdle(TIMEOUT);
+        device.waitForIdle(TIMEOUT);
+        bot.findRenameDialogOkButton().click();
+        device.waitForIdle(TIMEOUT);
 
-        mHelper.bot().assertDocument(UiTestEnvironment.fileName1, false);
-        mHelper.bot().assertDocument(newName, true);
-        mHelper.bot().assertDocumentsCount(mHelper.getDocumentsCountDir0());
+        bot.assertDocument(fileName1, false);
+        bot.assertDocument(newName, true);
+        bot.assertDocumentsCount(4);
     }
 
     public void testRenameFile_Enter() throws Exception {
-        mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
-        mHelper.bot().openOverflowMenu();
-        mHelper.bot().openDialog(R.string.menu_rename);
-        mHelper.bot().setDialogText(newName);
+        bot.selectDocument(fileName1);
+        bot.openOverflowMenu();
+        bot.openDialog(R.string.menu_rename);
+        bot.setDialogText(newName);
 
         pressEnter();
 
-        mHelper.bot().assertDocument(UiTestEnvironment.fileName1, false);
-        mHelper.bot().assertDocument(newName, true);
-        mHelper.bot().assertDocumentsCount(mHelper.getDocumentsCountDir0());
+        bot.assertDocument(fileName1, false);
+        bot.assertDocument(newName, true);
+        bot.assertDocumentsCount(4);
     }
 
     public void testRenameFile_Cancel() throws Exception {
-        mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
-        mHelper.bot().openOverflowMenu();
-        mHelper.bot().openDialog(R.string.menu_rename);
-        mHelper.bot().setDialogText(newName);
-        mHelper.bot().dismissKeyboardIfPresent();
+        bot.selectDocument(fileName1);
+        bot.openOverflowMenu();
+        bot.openDialog(R.string.menu_rename);
+        bot.setDialogText(newName);
+        bot.dismissKeyboardIfPresent();
 
-        mHelper.device().waitForIdle(TIMEOUT);
-        mHelper.bot().findRenameDialogCancelButton().click();
-        mHelper.device().waitForIdle(TIMEOUT);
+        device.waitForIdle(TIMEOUT);
+        bot.findRenameDialogCancelButton().click();
+        device.waitForIdle(TIMEOUT);
 
-        mHelper.bot().assertDocument(UiTestEnvironment.fileName1, true);
-        mHelper.bot().assertDocument(newName, false);
-        mHelper.bot().assertDocumentsCount(mHelper.getDocumentsCountDir0());
+        bot.assertDocument(fileName1, true);
+        bot.assertDocument(newName, false);
+        bot.assertDocumentsCount(4);
     }
 
     public void testRenameDir() throws Exception {
         String oldName = "Dir1";
         String newName = "Dir123";
 
-        mHelper.bot().selectDocument(oldName);
-        mHelper.bot().openOverflowMenu();
-        mHelper.bot().openDialog(R.string.menu_rename);
-        mHelper.bot().setDialogText(newName);
+        bot.selectDocument(oldName);
+        bot.openOverflowMenu();
+        bot.openDialog(R.string.menu_rename);
+        bot.setDialogText(newName);
 
         pressEnter();
 
-        mHelper.bot().assertDocument(oldName, false);
-        mHelper.bot().assertDocument(newName, true);
-        mHelper.bot().assertDocumentsCount(mHelper.getDocumentsCountDir0());
+        bot.assertDocument(oldName, false);
+        bot.assertDocument(newName, true);
+        bot.assertDocumentsCount(4);
     }
 
     public void testRename_NameExists() throws Exception {
         // Check that document with the new name exists
-        mHelper.bot().assertDocument(UiTestEnvironment.fileName2, true);
-        mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
-        mHelper.bot().openOverflowMenu();
-        mHelper.bot().openDialog(R.string.menu_rename);
-        mHelper.bot().setDialogText(UiTestEnvironment.fileName2);
+        bot.assertDocument(fileName2, true);
+        bot.selectDocument(fileName1);
+        bot.openOverflowMenu();
+        bot.openDialog(R.string.menu_rename);
+        bot.setDialogText(fileName2);
 
         pressEnter();
 
-        mHelper.bot().assertSnackbar(R.string.rename_error);
-        mHelper.bot().assertDocument(UiTestEnvironment.fileName1, true);
-        mHelper.bot().assertDocument(UiTestEnvironment.fileName2, true);
-        mHelper.bot().assertDocumentsCount(mHelper.getDocumentsCountDir0());
+        bot.assertSnackbar(R.string.rename_error);
+        bot.assertDocument(fileName1, true);
+        bot.assertDocument(fileName2, true);
+        bot.assertDocumentsCount(4);
     }
 
     private void pressEnter() {
-        mHelper.device().waitForIdle(TIMEOUT);
-        mHelper.device().pressEnter();
-        mHelper.device().waitForIdle(TIMEOUT);
+        device.waitForIdle(TIMEOUT);
+        device.pressEnter();
+        device.waitForIdle(TIMEOUT);
     }
-
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RootUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RootUiTest.java
new file mode 100644
index 0000000..1d1d3b5
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RootUiTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.MotionEvent;
+
+@LargeTest
+public class RootUiTest extends ActivityTest<FilesActivity> {
+
+    private static final String TAG = "RootUiTest";
+
+    public RootUiTest() {
+        super(FilesActivity.class);
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        initTestFiles();
+        bot.openRoot(ROOT_0_ID);
+    }
+
+    public void testRootTapped_GoToRootFromChildDir() throws Exception {
+        bot.openDocument(dirName1);
+        bot.assertWindowTitle(dirName1);
+        bot.openRoot(ROOT_0_ID);
+        bot.assertWindowTitle(ROOT_0_ID);
+        assertDefaultContentOfTestDir0();
+    }
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
index 042ec85..61da3df 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
@@ -24,138 +24,132 @@
 import android.test.suitebuilder.annotation.LargeTest;
 
 @LargeTest
-public class SearchViewUiTest extends InstrumentationTestCase {
+public class SearchViewUiTest extends ActivityTest<FilesActivity> {
 
     private static final String TAG = "SearchViewUiTest";
 
-    private UiTestEnvironment mEnv;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mEnv = new UiTestEnvironment(getInstrumentation());
-        mEnv.launch();
-
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mEnv.device().pressBack();
-        super.tearDown();
+    public SearchViewUiTest() {
+        super(FilesActivity.class);
     }
 
     public void testSearchView_ExpandsOnClick() throws Exception {
-        mEnv.bot().openSearchView();
-        mEnv.bot().assertSearchTextFiledAndIcon(true, false);
+        bot.openSearchView();
+        bot.assertSearchTextFiledAndIcon(true, false);
     }
 
     public void testSearchView_CollapsesOnBack() throws Exception {
-        mEnv.bot().openSearchView();
+        bot.openSearchView();
 
-        mEnv.device().pressBack();
+        device.pressBack();
 
-        mEnv.bot().assertSearchTextFiledAndIcon(false, true);
+        bot.assertSearchTextFiledAndIcon(false, true);
     }
 
     public void testSearchView_ClearsTextOnBack() throws Exception {
         String query = "file2";
-        mEnv.bot().openSearchView();
-        mEnv.bot().setSearchQuery(query);
+        bot.openSearchView();
+        bot.setSearchQuery(query);
 
-        mEnv.device().pressBack();
+        device.pressBack();
 
-        mEnv.bot().assertSearchTextFiledAndIcon(false, true);
+        bot.assertSearchTextFiledAndIcon(false, true);
     }
 
     public void testSearch_ResultsFound() throws Exception {
-        mEnv.initTestFiles();
-        mEnv.bot().openRoot(ROOT_0_ID);
-        mEnv.assertDefaultContentOfTestDir0();
+        initTestFiles();
+        bot.openRoot(ROOT_0_ID);
+        assertDefaultContentOfTestDir0();
 
         String query = "file1";
-        mEnv.bot().openSearchView();
-        mEnv.bot().setSearchQuery(query);
-        mEnv.bot().assertSearchTextField(true, query);
+        bot.openSearchView();
+        bot.setSearchQuery(query);
+        bot.assertSearchTextField(true, query);
 
-        mEnv.device().pressEnter();
+        device.pressEnter();
 
-        mEnv.bot().assertDocumentsCountOnList(true, 2);
-        mEnv.bot().assertHasDocuments(UiTestEnvironment.fileName1, UiTestEnvironment.fileName2);
-        mEnv.bot().assertSearchTextField(false, query);
+        bot.assertDocumentsCountOnList(true, 2);
+        bot.assertHasDocuments(fileName1, fileName2);
+
+        bot.assertSearchTextField(false, query);
     }
 
     public void testSearchResultsFound_ClearsOnBack() throws Exception {
-        mEnv.initTestFiles();
-        mEnv.bot().openRoot(ROOT_0_ID);
-        mEnv.assertDefaultContentOfTestDir0();
+        initTestFiles();
+        bot.openRoot(ROOT_0_ID);
+        assertDefaultContentOfTestDir0();
 
-        String query = UiTestEnvironment.fileName1;
-        mEnv.bot().openSearchView();
-        mEnv.bot().setSearchQuery(query);
+        String query = fileName1;
+        bot.openSearchView();
+        bot.setSearchQuery(query);
 
-        mEnv.device().pressEnter();
-        mEnv.device().pressBack();
-        mEnv.assertDefaultContentOfTestDir0();
+        device.pressEnter();
+        device.pressBack();
+
+        assertDefaultContentOfTestDir0();
     }
 
     public void testSearch_NoResults() throws Exception {
-        mEnv.initTestFiles();
-        mEnv.bot().openRoot(ROOT_0_ID);
-        mEnv.assertDefaultContentOfTestDir0();
+        initTestFiles();
+        bot.openRoot(ROOT_0_ID);
+        assertDefaultContentOfTestDir0();
 
         String query = "chocolate";
-        mEnv.bot().openSearchView();
-        mEnv.bot().setSearchQuery(query);
+        bot.openSearchView();
+        bot.setSearchQuery(query);
 
-        mEnv.device().pressEnter();
+        device.pressEnter();
 
-        mEnv.bot().assertDocumentsCountOnList(false, 0);
+        bot.assertDocumentsCountOnList(false, 0);
 
-        String msg = String.valueOf(mEnv.context().getString(R.string.no_results));
-        mEnv.bot().assertMessageTextView(String.format(msg, "TEST_ROOT_0"));
-        mEnv.bot().assertSearchTextField(false, query);
+        device.waitForIdle();
+        String msg = String.valueOf(context.getString(R.string.no_results));
+        bot.assertMessageTextView(String.format(msg, "TEST_ROOT_0"));
+
+        bot.assertSearchTextField(false, query);
     }
 
     public void testSearchNoResults_ClearsOnBack() throws Exception {
-        mEnv.initTestFiles();
-        mEnv.bot().openRoot(ROOT_0_ID);
-        mEnv.assertDefaultContentOfTestDir0();
+        initTestFiles();
+        bot.openRoot(ROOT_0_ID);
+        assertDefaultContentOfTestDir0();
 
         String query = "chocolate";
-        mEnv.bot().openSearchView();
-        mEnv.bot().setSearchQuery(query);
+        bot.openSearchView();
+        bot.setSearchQuery(query);
 
-        mEnv.device().pressEnter();
-        mEnv.device().pressBack();
-        mEnv.assertDefaultContentOfTestDir0();
+        device.pressEnter();
+        device.pressBack();
+
+        device.waitForIdle();
+        assertDefaultContentOfTestDir0();
     }
 
     public void testSearchResultsFound_ClearsOnDirectoryChange() throws Exception {
-        mEnv.initTestFiles();
-        mEnv.bot().openRoot(ROOT_0_ID);
-        mEnv.assertDefaultContentOfTestDir0();
+        initTestFiles();
+        bot.openRoot(ROOT_0_ID);
+        assertDefaultContentOfTestDir0();
 
-        String query = UiTestEnvironment.fileName1;
-        mEnv.bot().openSearchView();
-        mEnv.bot().setSearchQuery(query);
+        String query = fileName1;
+        bot.openSearchView();
+        bot.setSearchQuery(query);
 
-        mEnv.device().pressEnter();
+        device.pressEnter();
 
-        mEnv.bot().openRoot(ROOT_1_ID);
-        mEnv.assertDefaultContentOfTestDir1();
+        bot.openRoot(ROOT_1_ID);
+        assertDefaultContentOfTestDir1();
 
-        mEnv.bot().openRoot(ROOT_0_ID);
-        mEnv.assertDefaultContentOfTestDir0();
+        bot.openRoot(ROOT_0_ID);
+        assertDefaultContentOfTestDir0();
     }
 
     public void testSearchIconVisible_RootWithSearchSupport() throws Exception {
-        mEnv.bot().openRoot(ROOT_0_ID);
-        mEnv.bot().assertSearchTextFiledAndIcon(false, true);
+        bot.openRoot(ROOT_0_ID);
+        bot.assertSearchTextFiledAndIcon(false, true);
     }
 
     public void testSearchIconHidden_RootNoSearchSupport() throws Exception {
-        mEnv.bot().openRoot(ROOT_1_ID);
-        mEnv.bot().assertSearchTextFiledAndIcon(false, false);
+        bot.openRoot(ROOT_1_ID);
+        bot.assertSearchTextFiledAndIcon(false, false);
     }
 
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index d609fa84..6ba2146 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -24,6 +24,7 @@
 import android.content.Context;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.Configurator;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject;
 import android.support.test.uiautomator.UiObject2;
@@ -32,6 +33,7 @@
 import android.support.test.uiautomator.UiSelector;
 import android.support.test.uiautomator.Until;
 import android.util.Log;
+import android.view.MotionEvent;
 import android.view.inputmethod.InputMethodManager;
 
 import junit.framework.Assert;
@@ -47,8 +49,10 @@
  * programmatically, and making assertions against the state of the UI.
  */
 class UiBot {
+    public static final String TARGET_PKG = "com.android.documentsui";
 
     private static final String TAG = "UiBot";
+    private static final String LAUNCHER_PKG = "com.android.launcher";
 
     private static final BySelector SNACK_DELETE =
             By.desc(Pattern.compile("^Deleting [0-9]+ file.+"));
@@ -190,6 +194,14 @@
         assertNotNull(getSnackbar(mContext.getString(id)));
     }
 
+    void openDocument(String label) throws UiObjectNotFoundException {
+        int toolType = Configurator.getInstance().getToolType();
+        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_FINGER);
+        UiObject doc = findDocument(label);
+        doc.click();
+        Configurator.getInstance().setToolType(toolType);
+    }
+
     void clickDocument(String label) throws UiObjectNotFoundException {
         findDocument(label).click();
     }
@@ -390,4 +402,13 @@
         }
     }
 
+    void revealLauncher() {
+        mDevice.pressHome();
+        mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), mTimeout);
+    }
+
+    void revealApp() {
+        mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), mTimeout);
+        mDevice.waitForIdle();
+    }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiTestEnvironment.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiTestEnvironment.java
deleted file mode 100644
index 9e30589..0000000
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiTestEnvironment.java
+++ /dev/null
@@ -1,177 +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.documentsui;
-
-import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
-import static com.android.documentsui.StubProvider.ROOT_0_ID;
-import static com.android.documentsui.StubProvider.ROOT_1_ID;
-
-import android.app.Instrumentation;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.RemoteException;
-import android.provider.DocumentsContract.Document;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.Configurator;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.Until;
-import android.view.MotionEvent;
-
-import com.android.documentsui.model.RootInfo;
-
-/**
- * Provides basic test environment for UI tests:
- * - Launches activity
- * - Creates and gives access to test root directories and test files
- * - Cleans up the test environment
- */
-class UiTestEnvironment {
-
-    public static final int TIMEOUT = 5000;
-    public static final String NO_RENAME = "NO_RENAME";
-
-    public static final String dirName1 = "Dir1";
-    public static final String fileName1 = "file1.log";
-    public static final String fileName2 = "file12.png";
-    public static final String fileName3 = "anotherFile0.log";
-    public static final String fileName4 = "poodles.text";
-    public static final String fileNameNoRename = NO_RENAME + "file.txt";
-
-    private static final String TARGET_PKG = "com.android.documentsui";
-    private static final String LAUNCHER_PKG = "com.android.launcher";
-
-    private final UiBot mBot;
-    private final UiDevice mDevice;
-    private final Context mContext;
-
-    private  RootInfo mRootDir0;
-    private  RootInfo mRootDir1;
-    private int mDocsCountDir0;
-    private int mDocsCountDir1;
-
-    private ContentResolver mResolver;
-    private DocumentsProviderHelper mDocsHelper;
-    private ContentProviderClient mClient;
-
-    private final Instrumentation mInstrumentation;
-
-    public UiTestEnvironment(Instrumentation instrumentation) {
-        mInstrumentation = instrumentation;
-        mDevice = UiDevice.getInstance(mInstrumentation);
-        // NOTE: Must be the "target" context, else security checks in content provider will fail.
-        mContext = mInstrumentation.getTargetContext();
-        mBot = new UiBot(mDevice, mContext, TIMEOUT);
-    }
-
-/**
- * Launches default activity and waits for the application to appear.
- * @throws Exception
- */
-    public void launch() throws Exception {
-        Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
-        launch(intent);
-    }
-
-    /**
-     * Launches activity specified by intent and waits for the application to appear.
-     * @param intent Intent describing activity to launch.
-     * @throws Exception
-     */
-    public void launch(Intent intent) throws Exception {
-        Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
-        // Start from the home screen.
-        mDevice.pressHome();
-        mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), TIMEOUT);
-
-        mResolver = mContext.getContentResolver();
-        mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
-        mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
-
-        // Launch app.
-        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(intent);
-        // Wait for the app to appear.
-        mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
-        mDevice.waitForIdle();
-
-        resetStorage(); // Just incase a test failed and tearDown didn't happen.
-    }
-
-    public void cleanUp() throws Exception {
-        resetStorage();
-        mClient.release();
-    }
-
-    public void resetStorage() throws RemoteException {
-        mClient.call("clear", null, null);
-        mDevice.waitForIdle();
-    }
-
-    public void initTestFiles() throws RemoteException {
-        mRootDir0 = mDocsHelper.getRoot(ROOT_0_ID);
-        mRootDir1 = mDocsHelper.getRoot(ROOT_1_ID);
-
-        mDocsHelper.createFolder(mRootDir0, dirName1);
-        mDocsHelper.createDocument(mRootDir0, "text/plain", fileName1);
-        mDocsHelper.createDocument(mRootDir0, "image/png", fileName2);
-        mDocsHelper.createDocumentWithFlags(mRootDir0.documentId, "text/plain", fileNameNoRename,
-                Document.FLAG_SUPPORTS_WRITE);
-        mDocsCountDir0 = 4;
-
-        mDocsHelper.createDocument(mRootDir1, "text/plain", fileName3);
-        mDocsHelper.createDocument(mRootDir1, "text/plain", fileName4);
-        mDocsCountDir1 = 2;
-    }
-
-    public void assertDefaultContentOfTestDir0() throws UiObjectNotFoundException {
-        bot().assertDocumentsCount(ROOT_0_ID, getDocumentsCountDir0());
-        bot().assertHasDocuments(UiTestEnvironment.fileName1, UiTestEnvironment.fileName2,
-                UiTestEnvironment.dirName1, UiTestEnvironment.fileNameNoRename);
-    }
-
-    public void assertDefaultContentOfTestDir1() throws UiObjectNotFoundException {
-        bot().assertDocumentsCount(ROOT_1_ID, getDocumentsCountDir1());
-        bot().assertHasDocuments(UiTestEnvironment.fileName3, UiTestEnvironment.fileName4);
-    }
-
-    public UiBot bot() {
-        return mBot;
-    }
-
-    public UiDevice device() {
-        return mDevice;
-    }
-
-    public Context context() {
-        return mContext;
-    }
-
-    public RootInfo getRootDir0() {
-        return mRootDir0;
-    }
-
-    public int getDocumentsCountDir0() {
-        return mDocsCountDir0;
-    }
-
-    public int getDocumentsCountDir1() {
-        return mDocsCountDir1;
-    }
-}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index 3faa8f4..6af492c 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -73,7 +73,6 @@
                     extraValuesList,
                     COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
                     EMPTY_ARGS,
-                    /* heuristic */ false,
                     COLUMN_DEVICE_ID);
             database.setTransactionSuccessful();
             return changed;
@@ -92,16 +91,13 @@
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
         database.beginTransaction();
         try {
-            final boolean heuristic;
             final String mapColumn;
             Preconditions.checkState(mMappingMode.containsKey(parentDocumentId));
             switch (mMappingMode.get(parentDocumentId)) {
                 case MAP_BY_MTP_IDENTIFIER:
-                    heuristic = false;
                     mapColumn = COLUMN_STORAGE_ID;
                     break;
                 case MAP_BY_NAME:
-                    heuristic = true;
                     mapColumn = Document.COLUMN_DISPLAY_NAME;
                     break;
                 default:
@@ -120,7 +116,6 @@
                     extraValuesList,
                     COLUMN_PARENT_DOCUMENT_ID + "=?",
                     strings(parentDocumentId),
-                    heuristic,
                     mapColumn);
 
             database.setTransactionSuccessful();
@@ -137,16 +132,13 @@
      * @param documents List of document information.
      */
     synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
-        final boolean heuristic;
         final String mapColumn;
         Preconditions.checkState(mMappingMode.containsKey(parentId));
         switch (mMappingMode.get(parentId)) {
             case MAP_BY_MTP_IDENTIFIER:
-                heuristic = false;
                 mapColumn = COLUMN_OBJECT_HANDLE;
                 break;
             case MAP_BY_NAME:
-                heuristic = true;
                 mapColumn = Document.COLUMN_DISPLAY_NAME;
                 break;
             default:
@@ -163,17 +155,13 @@
                 null,
                 COLUMN_PARENT_DOCUMENT_ID + "=?",
                 strings(parentId),
-                heuristic,
                 mapColumn);
     }
 
-    @VisibleForTesting
     void clearMapping() {
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
         database.beginTransaction();
         try {
-            mDatabase.deleteDocumentsAndRootsRecursively(
-                    COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
             final ContentValues values = new ContentValues();
             values.putNull(COLUMN_OBJECT_HANDLE);
             values.putNull(COLUMN_STORAGE_ID);
@@ -209,11 +197,6 @@
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
         database.beginTransaction();
         try {
-            // Delete all pending rows.
-            mDatabase.deleteDocumentsAndRootsRecursively(
-                    selection + " AND " + COLUMN_ROW_STATE + "=?",
-                    DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_PENDING)));
-
             // Set all documents as invalidated.
             final ContentValues values = new ContentValues();
             values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
@@ -245,7 +228,6 @@
      * @param rootExtraValuesList Values for root extra to be stored in the database.
      * @param selection SQL where closure to select rows that shares the same parent.
      * @param args Argument for selection SQL.
-     * @param heuristic Whether the mapping mode is heuristic.
      * @return Whether the method adds new rows.
      */
     private boolean putDocuments(
@@ -253,7 +235,6 @@
             @Nullable ContentValues[] rootExtraValuesList,
             String selection,
             String[] args,
-            boolean heuristic,
             String mappingKey) {
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
         boolean added = false;
@@ -285,7 +266,7 @@
                     if (candidateCursor.getCount() == 0) {
                         rowId = database.insert(TABLE_DOCUMENTS, null, values);
                         added = true;
-                    } else if (!heuristic) {
+                    } else {
                         candidateCursor.moveToNext();
                         rowId = candidateCursor.getLong(0);
                         database.update(
@@ -293,9 +274,6 @@
                                 values,
                                 SELECTION_DOCUMENT_ID,
                                 strings(rowId));
-                    } else {
-                        values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
-                        rowId = database.insertOrThrow(TABLE_DOCUMENTS, null, values);
                     }
                     // Document ID is a primary integer key of the table. So the returned row
                     // IDs should be same with the document ID.
@@ -334,84 +312,10 @@
             selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
             args = EMPTY_ARGS;
         }
-        final String groupKey;
-        switch (mMappingMode.get(parentId)) {
-            case MAP_BY_MTP_IDENTIFIER:
-                groupKey = parentId != null ? COLUMN_OBJECT_HANDLE : COLUMN_STORAGE_ID;
-                break;
-            case MAP_BY_NAME:
-                groupKey = Document.COLUMN_DISPLAY_NAME;
-                break;
-            default:
-                throw new Error("Unexpected mapping state.");
-        }
         mMappingMode.remove(parentId);
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
         database.beginTransaction();
         try {
-            // Get 1-to-1 mapping of invalidated document and pending document.
-            final String invalidatedIdQuery = createStateFilter(
-                    ROW_STATE_INVALIDATED, Document.COLUMN_DOCUMENT_ID);
-            final String pendingIdQuery = createStateFilter(
-                    ROW_STATE_PENDING, Document.COLUMN_DOCUMENT_ID);
-            // SQL should be like:
-            // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
-            //        group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
-            // WHERE device_id = ? AND parent_document_id IS NULL
-            // GROUP BY display_name
-            // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
-            //        count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
-            final Cursor mergingCursor = database.query(
-                    TABLE_DOCUMENTS,
-                    new String[] {
-                            "group_concat(" + invalidatedIdQuery + ")",
-                            "group_concat(" + pendingIdQuery + ")"
-                    },
-                    selection,
-                    args,
-                    groupKey,
-                    "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
-                    null);
-
-            final ContentValues values = new ContentValues();
-            while (mergingCursor.moveToNext()) {
-                final String invalidatedId = mergingCursor.getString(0);
-                final String pendingId = mergingCursor.getString(1);
-
-                // Obtain the new values including the latest object handle from mapping row.
-                getFirstRow(
-                        TABLE_DOCUMENTS,
-                        SELECTION_DOCUMENT_ID,
-                        new String[] { pendingId },
-                        values);
-                values.remove(Document.COLUMN_DOCUMENT_ID);
-                values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
-                database.update(
-                        TABLE_DOCUMENTS,
-                        values,
-                        SELECTION_DOCUMENT_ID,
-                        new String[] { invalidatedId });
-
-                getFirstRow(
-                        TABLE_ROOT_EXTRA,
-                        SELECTION_ROOT_ID,
-                        new String[] { pendingId },
-                        values);
-                if (values.size() > 0) {
-                    values.remove(Root.COLUMN_ROOT_ID);
-                    database.update(
-                            TABLE_ROOT_EXTRA,
-                            values,
-                            SELECTION_ROOT_ID,
-                            new String[] { invalidatedId });
-                }
-
-                // Delete 'pending' row.
-                mDatabase.deleteDocumentsAndRootsRecursively(
-                        SELECTION_DOCUMENT_ID, new String[] { pendingId });
-            }
-            mergingCursor.close();
-
             boolean changed = false;
 
             // Delete all invalidated rows that cannot be mapped.
@@ -421,58 +325,10 @@
                 changed = true;
             }
 
-            // The database cannot find old document ID for the pending rows.
-            // Turn the all pending rows into valid state, which means the rows become to be
-            // valid with new document ID.
-            values.clear();
-            values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
-            if (database.update(
-                    TABLE_DOCUMENTS,
-                    values,
-                    COLUMN_ROW_STATE + " = ? AND " + selection,
-                    DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_PENDING), args)) != 0) {
-                changed = true;
-            }
             database.setTransactionSuccessful();
             return changed;
         } finally {
             database.endTransaction();
         }
     }
-
-    /**
-     * Obtains values of the first row for the query.
-     * @param values ContentValues that the values are stored to.
-     * @param table Target table.
-     * @param selection Query to select rows.
-     * @param args Argument for query.
-     */
-    private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
-        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
-        values.clear();
-        final Cursor cursor = database.query(table, null, selection, args, null, null, null, "1");
-        try {
-            if (cursor.getCount() == 0) {
-                return;
-            }
-            cursor.moveToNext();
-            DatabaseUtils.cursorRowToContentValues(cursor, values);
-        } finally {
-            cursor.close();
-        }
-    }
-
-    /**
-     * Gets SQL expression that represents the given value or NULL depends on the row state.
-     * You must pass static constants to this methods otherwise you may be suffered from SQL
-     * injections.
-     * @param state Expected row state.
-     * @param a SQL value.
-     * @return Expression that represents a if the row state is expected one, and represents NULL
-     *     otherwise.
-     */
-    private static String createStateFilter(int state, String a) {
-        return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
-                " THEN " + a + " ELSE NULL END";
-    }
 }
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
index 3cfb82f..33687cb 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -78,14 +78,6 @@
     static final int ROW_STATE_INVALIDATED = 1;
 
     /**
-     * The state represents the raw has a valid object handle but it may be going to be mapped with
-     * another rows invalidated. After fetching all documents under the parent, the database tries
-     * to map the pending documents and the invalidated documents in order to keep old document ID
-     * alive.
-     */
-    static final int ROW_STATE_PENDING = 2;
-
-    /**
      * Mapping mode that uses MTP identifier to find corresponding rows.
      */
     static final int MAP_BY_MTP_IDENTIFIER = 0;
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index a49dc67..a75012e 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -310,14 +310,14 @@
             assertEquals(3, cursor.getCount());
             cursor.moveToNext();
             assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
-            assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
+            assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
@@ -333,7 +333,7 @@
             assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
@@ -387,7 +387,7 @@
             assertEquals(4, cursor.getCount());
 
             cursor.moveToPosition(3);
-            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
             assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
 
@@ -406,7 +406,7 @@
             assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.moveToNext();
-            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
             assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
@@ -544,7 +544,7 @@
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
-            assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
+            assertEquals(201, getInt(cursor, COLUMN_OBJECT_HANDLE));
             cursor.close();
         }
     }
@@ -626,11 +626,12 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            // One reuses exisitng document ID 1.
+            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(201, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index f03e94d..231fc69 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -63,14 +63,15 @@
                 (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
         List<AppOpsManager.PackageOps> appOps = aoManager.getPackagesForOps(LOCATION_OPS);
 
+        final int appOpsCount = appOps != null ? appOps.size() : 0;
+
         // Process the AppOps list and generate a preference list.
-        ArrayList<Request> requests = new ArrayList<>(appOps.size());
+        ArrayList<Request> requests = new ArrayList<>(appOpsCount);
         final long now = System.currentTimeMillis();
         final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         final List<UserHandle> profiles = um.getUserProfiles();
 
-        final int appOpsN = appOps.size();
-        for (int i = 0; i < appOpsN; ++i) {
+        for (int i = 0; i < appOpsCount; ++i) {
             AppOpsManager.PackageOps ops = appOps.get(i);
             // Don't show the Android System in the list - it's not actionable for the user.
             // Also don't show apps belonging to background users except managed users.
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index e5e5710..6702cef 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -282,9 +282,6 @@
     <!-- The padding between freeform workspace tasks -->
     <dimen name="recents_freeform_workspace_task_padding">8dp</dimen>
 
-    <!-- Space reserved for the cards behind the top card in the top stack -->
-    <dimen name="top_stack_peek_amount">12dp</dimen>
-
     <!-- Space reserved for the cards behind the top card in the bottom stack -->
     <dimen name="bottom_stack_peek_amount">12dp</dimen>
 
@@ -295,9 +292,6 @@
     <!-- The height of the area before the bottom stack in which the notifications slow down -->
     <dimen name="bottom_stack_slow_down_length">12dp</dimen>
 
-    <!-- The height of the area before the top stack in which the notifications slow down -->
-    <dimen name="top_stack_slow_down_length">12dp</dimen>
-
     <!-- Z distance between notifications if they are in the stack -->
     <dimen name="z_distance_between_notifications">0.5dp</dimen>
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 3b49d37..9d4f425 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -240,11 +240,11 @@
     /**
      * Dismisses recents if we are already visible and the intent is to toggle the recents view.
      */
-    boolean dismissRecentsToFocusedTask() {
+    boolean dismissRecentsToFocusedTask(int logCategory) {
         SystemServicesProxy ssp = Recents.getSystemServices();
         if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
             // If we have a focused Task, launch that Task now
-            if (mRecentsView.launchFocusedTask()) return true;
+            if (mRecentsView.launchFocusedTask(logCategory)) return true;
         }
         return false;
     }
@@ -270,7 +270,7 @@
         SystemServicesProxy ssp = Recents.getSystemServices();
         if (ssp.isRecentsTopMost(ssp.getTopMostTask(), null)) {
             // If we have a focused Task, launch that Task now
-            if (mRecentsView.launchFocusedTask()) return true;
+            if (mRecentsView.launchFocusedTask(0 /* logCategory */)) return true;
             // If none of the other cases apply, then just go Home
             dismissRecentsToHome(true /* animateTaskViews */);
             return true;
@@ -360,7 +360,7 @@
         mIterateTrigger = new DozeTrigger(mFocusTimerDuration, new Runnable() {
             @Override
             public void run() {
-                dismissRecentsToFocusedTask();
+                dismissRecentsToFocusedTask(MetricsEvent.OVERVIEW_SELECT_TIMEOUT);
             }
         });
 
@@ -634,7 +634,7 @@
             // Focus the next task
             EventBus.getDefault().send(new FocusNextTaskViewEvent(timerIndicatorDuration));
 
-            MetricsLogger.action(this, MetricsEvent.ACTION_OVERVIEW_PAGE);
+            MetricsLogger.action(this, MetricsEvent.OVERVIEW_PAGE);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
index f7ebd94..82e7861 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivityLaunchState.java
@@ -66,8 +66,9 @@
      */
     public int getInitialFocusTaskIndex(int numTasks) {
         RecentsDebugFlags debugFlags = Recents.getDebugFlags();
+        RecentsActivityLaunchState launchState = Recents.getConfiguration().getLaunchState();
         if (launchedFromAppWithThumbnail) {
-            if (debugFlags.isFastToggleRecentsEnabled()) {
+            if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
                 // If fast toggling, focus the front most task so that the next tap will focus the
                 // N-1 task
                 return numTasks - 1;
@@ -76,7 +77,7 @@
             // If coming from another app, focus the next task
             return numTasks - 2;
         } else {
-            if (debugFlags.isFastToggleRecentsEnabled()) {
+            if (!launchState.launchedWithAltTab && debugFlags.isFastToggleRecentsEnabled()) {
                 // If fast toggling, defer focusing until the next tap (which will automatically
                 // focus the front most task)
                 return -1;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index 5890b5f..9da5c2b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -124,6 +124,10 @@
         public void onPinnedActivityRestartAttempt() {
         }
 
+        @Override
+        public void onPinnedStackAnimationEnded() {
+        }
+
         /** Preloads the next task */
         public void run() {
             RecentsConfiguration config = Recents.getConfiguration();
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
index 5eeda72..d7b9b9e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryAdapter.java
@@ -161,7 +161,7 @@
             ssp.startActivityFromRecents(v.getContext(), task.key.id, task.title,
                     ActivityOptions.makeBasic());
 
-            MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT,
+            MetricsLogger.action(v.getContext(), MetricsEvent.OVERVIEW_SELECT,
                     task.key.getComponent().toString());
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
index acad0ea..3d1ea8e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/history/RecentsHistoryItemTouchCallbacks.java
@@ -21,6 +21,7 @@
 import android.support.v7.widget.helper.ItemTouchHelper;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.systemui.recents.Constants;
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.ui.DeleteTaskDataEvent;
@@ -72,6 +73,8 @@
             // Keep track of deletions by swiping within history
             MetricsLogger.histogram(mContext, "overview_task_dismissed_source",
                     Constants.Metrics.DismissSourceHistorySwipeGesture);
+            MetricsLogger.action(mContext, MetricsEvent.OVERVIEW_DISMISS,
+                    taskRow.task.key.getComponent().toString());
         }
     }
 }
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 4deea54..52043f4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java
@@ -62,6 +62,8 @@
                 }
             };
 
+    public static final RectFEvaluator RECTF_EVALUATOR = new RectFEvaluator();
+
     /**
      * @return the first parent walking up the view hierarchy that has the given class type.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
index 42ebfa9..f3201d0 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/tv/RecentsTvActivity.java
@@ -318,13 +318,13 @@
         switch (keyCode) {
             case KeyEvent.KEYCODE_DPAD_UP: {
                 SystemServicesProxy ssp = Recents.getSystemServices();
-                PipManager.getInstance().showPipMenu();
+                PipManager.getInstance().resizePinnedStack(PipManager.STATE_PIP_MENU);
                 ssp.focusPinnedStack();
                 return true;
             }
             case KeyEvent.KEYCODE_DPAD_DOWN: {
                 SystemServicesProxy ssp = Recents.getSystemServices();
-                PipManager.getInstance().showPipOverlay(false);
+                PipManager.getInstance().resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
                 ssp.focusHomeStack();
                 return true;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index d4624f5..42aaa97 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -248,13 +248,18 @@
     }
 
     /** Launches the focused task from the first stack if possible */
-    public boolean launchFocusedTask() {
+    public boolean launchFocusedTask(int logEvent) {
         if (mTaskStackView != null) {
             Task task = mTaskStackView.getFocusedTask();
             if (task != null) {
                 TaskView taskView = mTaskStackView.getChildViewForTask(task);
                 EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
                         INVALID_STACK_ID, false));
+
+                if (logEvent != 0) {
+                    MetricsLogger.action(getContext(), logEvent,
+                            task.key.getComponent().toString());
+                }
                 return true;
             }
         }
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 7eaa193..76972d7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackAnimationHelper.java
@@ -23,6 +23,7 @@
 import android.graphics.Path;
 import android.graphics.RectF;
 import android.view.View;
+import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
 import com.android.systemui.Interpolators;
@@ -34,6 +35,7 @@
 import com.android.systemui.recents.model.Task;
 import com.android.systemui.recents.model.TaskStack;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -81,9 +83,18 @@
     private static final PathInterpolator EXIT_TO_HOME_ALPHA_INTERPOLATOR =
             new PathInterpolator(0.4f, 0, 1f, 1f);
 
+    private static final PathInterpolator FOCUS_NEXT_TASK_INTERPOLATOR =
+            new PathInterpolator(0.4f, 0, 0, 1f);
+    private static final PathInterpolator FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR =
+            new PathInterpolator(0, 0, 0, 1f);
+    private static final PathInterpolator FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR =
+            new PathInterpolator(0.4f, 0, 0.2f, 1f);
+
     private TaskStackView mStackView;
 
     private TaskViewTransform mTmpTransform = new TaskViewTransform();
+    private ArrayList<TaskViewTransform> mTmpCurrentTaskTransforms = new ArrayList<>();
+    private ArrayList<TaskViewTransform> mTmpFinalTaskTransforms = new ArrayList<>();
 
     public TaskStackAnimationHelper(Context context, TaskStackView stackView) {
         mStackView = stackView;
@@ -418,4 +429,92 @@
             mStackView.updateTaskViewToTransform(tv, mTmpTransform, taskAnimation);
         }
     }
+
+    /**
+     * Starts the animation to focus the next {@link TaskView} when paging through recents.
+     *
+     * @return whether or not this will trigger a scroll in the stack
+     */
+    public boolean startScrollToFocusedTaskAnimation(Task newFocusedTask,
+            boolean requestViewFocus) {
+        TaskStackLayoutAlgorithm stackLayout = mStackView.getStackAlgorithm();
+        TaskStackViewScroller stackScroller = mStackView.getScroller();
+        TaskStack stack = mStackView.getStack();
+
+        final float newScroll = stackLayout.getStackScrollForTask(newFocusedTask);
+        boolean willScrollToFront = newScroll > stackScroller.getStackScroll();
+        boolean willScroll = Float.compare(newScroll, stackScroller.getStackScroll()) != 0;
+
+        // Get the current set of task transforms
+        ArrayList<Task> stackTasks = stack.getStackTasks();
+        mStackView.getCurrentTaskTransforms(stackTasks, mTmpCurrentTaskTransforms);
+
+        // Pick up the newly visible views after the scroll
+        mStackView.bindVisibleTaskViews(newScroll);
+
+        // Update the internal state
+        stackLayout.setFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
+        stackScroller.setStackScroll(newScroll, null /* animation */);
+        mStackView.cancelDeferredTaskViewLayoutAnimation();
+
+        // Get the final set of task transforms
+        mStackView.getLayoutTaskTransforms(newScroll, stackTasks, mTmpFinalTaskTransforms);
+
+        // Focus the task view
+        TaskView newFocusedTaskView = mStackView.getChildViewForTask(newFocusedTask);
+        newFocusedTaskView.setFocusedState(true, requestViewFocus);
+
+        // Setup the end listener to return all the hidden views to the view pool after the
+        // focus animation
+        AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mStackView.bindVisibleTaskViews(newScroll);
+            }
+        };
+
+        List<TaskView> taskViews = mStackView.getTaskViews();
+        int taskViewCount = taskViews.size();
+        int newFocusTaskViewIndex = taskViews.indexOf(newFocusedTaskView);
+        for (int i = 0; i < taskViewCount; i++) {
+            TaskView tv = taskViews.get(i);
+            Task task = tv.getTask();
+
+            if (mStackView.isIgnoredTask(task)) {
+                continue;
+            }
+
+            int taskIndex = stackTasks.indexOf(task);
+            TaskViewTransform fromTransform = mTmpCurrentTaskTransforms.get(taskIndex);
+            TaskViewTransform toTransform = mTmpFinalTaskTransforms.get(taskIndex);
+
+            // Update the task to the initial state (for the newly picked up tasks)
+            mStackView.updateTaskViewToTransform(tv, fromTransform, AnimationProps.IMMEDIATE);
+
+            int duration;
+            Interpolator interpolator;
+            if (willScrollToFront) {
+                duration = Math.max(100, 100 + ((i - 1) * 50));
+                interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
+            } else {
+                if (i < newFocusTaskViewIndex) {
+                    duration = 150 + ((newFocusTaskViewIndex - i - 1) * 50);
+                    interpolator = FOCUS_BEHIND_NEXT_TASK_INTERPOLATOR;
+                } else if (i > newFocusTaskViewIndex) {
+                    duration = Math.max(100, 150 - ((i - newFocusTaskViewIndex - 1) * 50));
+                    interpolator = FOCUS_IN_FRONT_NEXT_TASK_INTERPOLATOR;
+                } else {
+                    duration = 200;
+                    interpolator = FOCUS_NEXT_TASK_INTERPOLATOR;
+                }
+            }
+
+            AnimationProps anim = new AnimationProps()
+                    .setDuration(AnimationProps.BOUNDS, duration)
+                    .setInterpolator(AnimationProps.BOUNDS, interpolator)
+                    .setListener(endListener);
+            mStackView.updateTaskViewToTransform(tv, toTransform, anim);
+        }
+        return willScroll;
+    }
 }
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 bd37c3b..19ac1e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -457,7 +457,7 @@
                             launchTaskIndex - 1));
                 }
             } else {
-                float offsetPct = (float) (mTaskRect.height() / 2) / mStackRect.height();
+                float offsetPct = (float) (mTaskRect.height() / 3) / mStackRect.height();
                 float normX = mUnfocusedCurveInterpolator.getX(offsetPct);
                 mInitialScrollP = Math.max(mMinScrollP, Math.min(mMaxScrollP,
                         launchTaskIndex - mUnfocusedRange.getAbsoluteX(normX)));
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 bb74de4..fb3515a 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -20,6 +20,8 @@
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.content.ComponentName;
@@ -41,8 +43,12 @@
 import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
 import android.widget.FrameLayout;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
@@ -113,6 +119,7 @@
 
     private static final ArraySet<Task.TaskKey> EMPTY_TASK_SET = new ArraySet<>();
 
+    LayoutInflater mInflater;
     TaskStack mStack;
     TaskStackLayoutAlgorithm mLayoutAlgorithm;
     TaskStackViewScroller mStackScroller;
@@ -142,16 +149,15 @@
     boolean mScreenPinningEnabled;
 
     // The stable stack bounds are the full bounds that we were measured with from RecentsView
-    Rect mStableStackBounds = new Rect();
+    private Rect mStableStackBounds = new Rect();
     // The current stack bounds are dynamic and may change as the user drags and drops
-    Rect mStackBounds = new Rect();
+    private Rect mStackBounds = new Rect();
 
-    int[] mTmpVisibleRange = new int[2];
-    Rect mTmpRect = new Rect();
-    ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
-    List<TaskView> mTmpTaskViews = new ArrayList<>();
-    TaskViewTransform mTmpTransform = new TaskViewTransform();
-    LayoutInflater mInflater;
+    private int[] mTmpVisibleRange = new int[2];
+    private Rect mTmpRect = new Rect();
+    private ArrayMap<Task.TaskKey, TaskView> mTmpTaskViewMap = new ArrayMap<>();
+    private List<TaskView> mTmpTaskViews = new ArrayList<>();
+    private TaskViewTransform mTmpTransform = new TaskViewTransform();
 
     // A convenience update listener to request updating clipping of tasks
     private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
@@ -396,6 +402,7 @@
         int frontMostVisibleIndex = -1;
         int backMostVisibleIndex = -1;
         boolean useTargetStackScroll = Float.compare(curStackScroll, targetStackScroll) != 0;
+        boolean targetScrollIsInFront = targetStackScroll > curStackScroll;
 
         // We can reuse the task transforms where possible to reduce object allocation
         Utilities.matchTaskListSize(tasks, taskTransforms);
@@ -439,7 +446,7 @@
                     frontMostVisibleIndex = i;
                 }
                 backMostVisibleIndex = i;
-            } else {
+            } else if (!targetScrollIsInFront) {
                 if (backMostVisibleIndex != -1) {
                     // We've reached the end of the visible range, so going down the rest of the
                     // stack, we can just reset the transforms accordingly
@@ -531,7 +538,7 @@
             }
 
             // Skip the invisible non-freeform stack tasks
-            if (i > visibleStackRange[0] && !task.isFreeformTask()) {
+            if (!task.isFreeformTask() && !transform.visible) {
                 continue;
             }
 
@@ -671,12 +678,20 @@
         for (int i = tasks.size() - 1; i >= 0; i--) {
             Task task = tasks.get(i);
             TaskViewTransform transform = transformsOut.get(i);
-            mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null);
+            mLayoutAlgorithm.getStackTransform(task, stackScroll, transform, null,
+                    true /* forceUpdate */);
             transform.visible = true;
         }
     }
 
     /**
+     * Cancels the next deferred task view layout.
+     */
+    void cancelDeferredTaskViewLayoutAnimation() {
+        mDeferredTaskViewLayoutAnimation = null;
+    }
+
+    /**
      * Cancels all {@link TaskView} animations.
      *
      * @see #cancelAllTaskViewAnimations(ArraySet<Task.TaskKey>)
@@ -716,7 +731,7 @@
             TaskView frontTv = null;
             int clipBottom = 0;
 
-            if (mIgnoreTasks.contains(tv.getTask().key)) {
+            if (isIgnoredTask(tv.getTask())) {
                 // For each of the ignore tasks, update the translationZ of its TaskView to be
                 // between the translationZ of the tasks immediately underneath it
                 if (prevVisibleTv != null) {
@@ -804,15 +819,15 @@
     }
 
     /**
-     * Sets the focused task to the provided (bounded taskIndex).
+     * Sets the focused task to the provided (bounded focusTaskIndex).
      *
      * @return whether or not the stack will scroll as a part of this focus change
      */
-    private boolean setFocusedTask(int taskIndex, boolean scrollToTask,
-            final boolean requestViewFocus, final int timerIndicatorDuration) {
+    private boolean setFocusedTask(int focusTaskIndex, boolean scrollToTask,
+            boolean requestViewFocus, int timerIndicatorDuration) {
         // Find the next task to focus
         int newFocusedTaskIndex = mStack.getTaskCount() > 0 ?
-                Math.max(0, Math.min(mStack.getTaskCount() - 1, taskIndex)) : -1;
+                Math.max(0, Math.min(mStack.getTaskCount() - 1, focusTaskIndex)) : -1;
         final Task newFocusedTask = (newFocusedTaskIndex != -1) ?
                 mStack.getStackTasks().get(newFocusedTaskIndex) : null;
 
@@ -830,7 +845,6 @@
         }
 
         boolean willScroll = false;
-
         mFocusedTask = newFocusedTask;
 
         if (newFocusedTask != null) {
@@ -845,33 +859,20 @@
                 }
             }
 
-            Runnable focusTaskRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    final TaskView tv = getChildViewForTask(newFocusedTask);
-                    if (tv != null) {
-                        tv.setFocusedState(true, requestViewFocus);
-                    }
-                }
-            };
-
             if (scrollToTask) {
                 // Cancel any running enter animations at this point when we scroll or change focus
                 if (!mEnterAnimationComplete) {
                     cancelAllTaskViewAnimations();
                 }
 
-                // TODO: Center the newly focused task view, only if not freeform
-                float newScroll = mLayoutAlgorithm.getStackScrollForTask(newFocusedTask);
-                if (Float.compare(newScroll, mStackScroller.getStackScroll()) != 0) {
-                    mStackScroller.animateScroll(newScroll, focusTaskRunnable);
-                    willScroll = true;
-                } else {
-                    focusTaskRunnable.run();
-                }
-                mLayoutAlgorithm.animateFocusState(TaskStackLayoutAlgorithm.STATE_FOCUSED);
+                willScroll = mAnimationHelper.startScrollToFocusedTaskAnimation(newFocusedTask,
+                        requestViewFocus);
             } else {
-                focusTaskRunnable.run();
+                // Focus the task view
+                TaskView newFocusedTaskView = getChildViewForTask(newFocusedTask);
+                if (newFocusedTaskView != null) {
+                    newFocusedTaskView.setFocusedState(true, requestViewFocus);
+                }
             }
         }
         return willScroll;
@@ -1276,7 +1277,7 @@
             Task task = tasks.get(i);
 
             // Ignore deleting tasks
-            if (mIgnoreTasks.contains(task.key)) {
+            if (isIgnoredTask(task)) {
                 if (i == tasks.size() - 1) {
                     isFrontMostTask.value = true;
                 }
@@ -1390,7 +1391,7 @@
     }
 
     @Override
-    public void prepareViewToEnterPool(TaskView tv) {
+    public void onReturnViewToPool(TaskView tv) {
         final Task task = tv.getTask();
 
         // Report that this tasks's data is no longer being used
@@ -1411,7 +1412,7 @@
     }
 
     @Override
-    public void prepareViewToLeavePool(TaskView tv, Task task, boolean isNewView) {
+    public void onPickUpViewFromPool(TaskView tv, Task task, boolean isNewView) {
         // Find the index where this task should be placed in the stack
         int taskIndex = mStack.indexOfStackTask(task);
         int insertIndex = findTaskViewInsertIndex(task, taskIndex);
@@ -1601,6 +1602,9 @@
     public final void onBusEvent(TaskViewDismissedEvent event) {
         removeTaskViewFromStack(event.taskView, event.task);
         EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
+
+        MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS,
+                event.task.key.getComponent().toString());
     }
 
     public final void onBusEvent(FocusNextTaskViewEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
index c641d75..d1bce55 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewScroller.java
@@ -191,21 +191,27 @@
         stopScroller();
         stopBoundScrollAnimation();
 
-        mFinalAnimatedScroll = newScroll;
-        mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll);
-        mScrollAnimator.setDuration(mContext.getResources().getInteger(
-                R.integer.recents_animate_task_stack_scroll_duration));
-        mScrollAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-        mScrollAnimator.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (postRunnable != null) {
-                    postRunnable.run();
+        if (Float.compare(mStackScrollP, newScroll) != 0) {
+            mFinalAnimatedScroll = newScroll;
+            mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll);
+            mScrollAnimator.setDuration(mContext.getResources().getInteger(
+                    R.integer.recents_animate_task_stack_scroll_duration));
+            mScrollAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+            mScrollAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (postRunnable != null) {
+                        postRunnable.run();
+                    }
+                    mScrollAnimator.removeAllListeners();
                 }
-                mScrollAnimator.removeAllListeners();
+            });
+            mScrollAnimator.start();
+        } else {
+            if (postRunnable != null) {
+                postRunnable.run();
             }
-        });
-        mScrollAnimator.start();
+        }
     }
 
     /** Aborts any current stack scrolls */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
index d6680fd..5d1bb66 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackViewTouchHandler.java
@@ -34,6 +34,7 @@
 import android.view.animation.PathInterpolator;
 
 import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.SwipeHelper;
@@ -58,8 +59,6 @@
 class TaskStackViewTouchHandler implements SwipeHelper.Callback {
 
     private static final int INACTIVE_POINTER_ID = -1;
-
-    private static final RectFEvaluator RECT_EVALUATOR = new RectFEvaluator();
     private static final Interpolator STACK_TRANSFORM_INTERPOLATOR =
             new PathInterpolator(0.73f, 0.33f, 0.42f, 0.85f);
 
@@ -230,6 +229,8 @@
                         if (parent != null) {
                             parent.requestDisallowInterceptTouchEvent(true);
                         }
+
+                        MetricsLogger.action(mSv.getContext(), MetricsEvent.OVERVIEW_SCROLL);
                     }
                 }
                 if (mIsScrolling) {
@@ -542,8 +543,8 @@
 
             mTmpTransform.copyFrom(fromTransform);
             // We only really need to interpolate the bounds, progress and translation
-            mTmpTransform.rect.set(RECT_EVALUATOR.evaluate(dismissFraction, fromTransform.rect,
-                    toTransform.rect));
+            mTmpTransform.rect.set(Utilities.RECTF_EVALUATOR.evaluate(dismissFraction,
+                    fromTransform.rect, toTransform.rect));
             mTmpTransform.p = fromTransform.p + (toTransform.p - fromTransform.p) * dismissFraction;
             mTmpTransform.translationZ = fromTransform.translationZ +
                     (toTransform.translationZ - fromTransform.translationZ) * dismissFraction;
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 439d96f..850e36e7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -38,6 +38,8 @@
 import android.view.ViewOutlineProvider;
 import android.view.animation.AccelerateInterpolator;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.recents.Recents;
@@ -585,6 +587,9 @@
         }
         EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, INVALID_STACK_ID,
                 screenPinningRequested));
+
+        MetricsLogger.action(v.getContext(), MetricsEvent.OVERVIEW_SELECT,
+                mTask.key.getComponent().toString());
     }
 
     /**** View.OnLongClickListener Implementation ****/
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
index 31fbd3e..a287fe6 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/ViewPool.java
@@ -29,8 +29,8 @@
     /* An interface to the consumer of a view pool */
     public interface ViewPoolConsumer<V, T> {
         public V createView(Context context);
-        public void prepareViewToEnterPool(V v);
-        public void prepareViewToLeavePool(V v, T prepareData, boolean isNewView);
+        public void onReturnViewToPool(V v);
+        public void onPickUpViewFromPool(V v, T prepareData, boolean isNewView);
         public boolean hasPreferredData(V v, T preferredData);
     }
 
@@ -46,7 +46,7 @@
 
     /** Returns a view into the pool */
     void returnViewToPool(V v) {
-        mViewCreator.prepareViewToEnterPool(v);
+        mViewCreator.onReturnViewToPool(v);
         mPool.push(v);
     }
 
@@ -73,7 +73,7 @@
                 v = mPool.pop();
             }
         }
-        mViewCreator.prepareViewToLeavePool(v, prepareData, isNewView);
+        mViewCreator.onPickUpViewFromPool(v, prepareData, isNewView);
         return v;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 7f1316f..84b2031 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -947,6 +947,10 @@
         }
     }
 
+    public boolean mustStayOnScreen() {
+        return mIsHeadsUp;
+    }
+
     private void updateClearability() {
         // public versions cannot be dismissed
         mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index a0fb34a..8042b60 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -399,6 +399,10 @@
         return false;
     }
 
+    public boolean mustStayOnScreen() {
+        return false;
+    }
+
     /**
      * A listener notifying when {@link #getActualHeight} changes.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
index 5abd1d5..dd6d6f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGuts.java
@@ -35,6 +35,8 @@
 import android.widget.SeekBar;
 import android.widget.TextView;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.MetricsProto.MetricsEvent;
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 
@@ -51,6 +53,7 @@
     private SeekBar mSeekBar;
     private Notification.Topic mTopic;
     private INotificationManager mINotificationManager;
+    private int mStartingImportance;
 
     public NotificationGuts(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -103,6 +106,7 @@
 
     void bindImportance(final StatusBarNotification sbn, final ExpandableNotificationRow row,
             final int importance) {
+        mStartingImportance = importance;
         mINotificationManager = INotificationManager.Stub.asInterface(
                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
         mTopic = sbn.getNotification().getTopic() == null
@@ -151,6 +155,7 @@
                 }
                 updateTitleAndSummary(progress);
                 if (fromUser) {
+                    MetricsLogger.action(mContext, MetricsEvent.ACTION_MODIFY_IMPORTANCE_SLIDER);
                     if (appUsesTopics) {
                         mApplyToTopic.setVisibility(View.VISIBLE);
                         mApplyToTopic.setText(
@@ -205,6 +210,8 @@
 
     void saveImportance(final StatusBarNotification sbn) {
         int progress = mSeekBar.getProgress();
+        MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE,
+                progress - mStartingImportance);
         try {
             mINotificationManager.setImportance(sbn.getPackageName(), sbn.getUid(),
                     mApplyToTopic.isChecked() ? mTopic : null, progress);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index e20936b..08cd053 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -109,6 +109,10 @@
         }
 
         @Override
+        public void onPinnedStackAnimationEnded() {
+        }
+
+        @Override
         public void onTaskStackChanged() {
             mHandler.removeCallbacks(this);
             mHandler.post(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index b5b7f43..79c21f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -79,7 +79,8 @@
                 mTouchingHeadsUpView = false;
                 if (child instanceof ExpandableNotificationRow) {
                     mPickedChild = (ExpandableNotificationRow) child;
-                    mTouchingHeadsUpView = mPickedChild.isHeadsUp() && mPickedChild.isPinned();
+                    mTouchingHeadsUpView = !mStackScroller.isExpanded()
+                            && mPickedChild.isHeadsUp() && mPickedChild.isPinned();
                 }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 5f5974e..0febbd2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -738,9 +738,9 @@
                 + (offscreen ? " OFFSCREEN!" : ""));
 
         pw.println(String.format("      mCurrentView: id=%s (%dx%d) %s",
-                        getResourceName(mCurrentView.getId()),
-                        mCurrentView.getWidth(), mCurrentView.getHeight(),
-                        visibilityToString(mCurrentView.getVisibility())));
+                        getResourceName(getCurrentView().getId()),
+                        getCurrentView().getWidth(), getCurrentView().getHeight(),
+                        visibilityToString(getCurrentView().getVisibility())));
 
         pw.println(String.format("      disabled=0x%08x vertical=%s menu=%s",
                         mDisabledFlags,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index 09a7bf0..50a49a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -519,7 +519,7 @@
      */
     protected boolean mStartedGoingToSleep;
 
-    private static final int VISIBLE_LOCATIONS = StackViewState.LOCATION_FIRST_CARD
+    private static final int VISIBLE_LOCATIONS = StackViewState.LOCATION_FIRST_HUN
             | StackViewState.LOCATION_MAIN_AREA;
 
     private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index cc0e67d..49e9c3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -75,7 +75,7 @@
         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
-    private static final String TAG = "NotificationStackScrollLayout";
+    private static final String TAG = "StackScroller";
     private static final boolean DEBUG = false;
     private static final float RUBBER_BAND_FACTOR_NORMAL = 0.35f;
     private static final float RUBBER_BAND_FACTOR_AFTER_EXPAND = 0.15f;
@@ -136,7 +136,7 @@
     private StackScrollState mCurrentStackScrollState = new StackScrollState(this);
     private AmbientState mAmbientState = new AmbientState();
     private NotificationGroupManager mGroupManager;
-    private ArrayList<View> mChildrenToAddAnimated = new ArrayList<>();
+    private HashSet<View> mChildrenToAddAnimated = new HashSet<>();
     private ArrayList<View> mAddedHeadsUpChildren = new ArrayList<>();
     private ArrayList<View> mChildrenToRemoveAnimated = new ArrayList<>();
     private ArrayList<View> mSnappedBackChildren = new ArrayList<>();
@@ -474,6 +474,7 @@
      * modifications to {@link #mOwnScrollY} are performed to reflect it in the view layout.
      */
     private void updateChildren() {
+        updateScrollStateForAddedChildren();
         mAmbientState.setScrollY(mOwnScrollY);
         mStackScrollAlgorithm.getStackScrollState(mAmbientState, mCurrentStackScrollState);
         if (!isCurrentlyAnimating() && !mNeedsAnimation) {
@@ -483,6 +484,28 @@
         }
     }
 
+    private void updateScrollStateForAddedChildren() {
+        if (mChildrenToAddAnimated.isEmpty()) {
+            return;
+        }
+        for (int i = 0; i < getChildCount(); i++) {
+            ExpandableView child = (ExpandableView) getChildAt(i);
+            if (mChildrenToAddAnimated.contains(child)) {
+                int startingPosition = getPositionInLinearLayout(child);
+                int padding = child.needsIncreasedPadding()
+                        ? mIncreasedPaddingBetweenElements :
+                        mPaddingBetweenElements;
+                int childHeight = getIntrinsicHeight(child) + padding;
+                if (startingPosition < mOwnScrollY) {
+                    // This child starts off screen, so let's keep it offscreen to keep the others visible
+
+                    mOwnScrollY += childHeight;
+                }
+            }
+        }
+        clampScrollPosition();
+    }
+
     private void requestChildrenUpdate() {
         if (!mChildrenUpdateRequested) {
             getViewTreeObserver().addOnPreDrawListener(mChildrenUpdater);
@@ -1648,12 +1671,17 @@
                 bottom = (int) (lastView.getTranslationY() + lastView.getActualHeight());
                 bottom = Math.min(bottom, getHeight());
             }
-        } else if (mPhoneStatusBar.getBarState() == StatusBarState.KEYGUARD) {
-            top = mTopPadding;
+        } else {
+            top = (int) (mTopPadding + mStackTranslation);
             bottom = top;
         }
-        mBackgroundBounds.top = Math.max(0, top);
-        mBackgroundBounds.bottom = Math.min(getHeight(), bottom);
+        if (mPhoneStatusBar.getBarState() != StatusBarState.KEYGUARD) {
+            mBackgroundBounds.top = (int) Math.max(mTopPadding + mStackTranslation, top);
+        } else {
+            // otherwise the animation from the shade to the keyguard will jump as it's maxed
+            mBackgroundBounds.top = Math.max(0, top);
+        }
+        mBackgroundBounds.bottom = Math.min(getHeight(), Math.max(bottom, top));
     }
 
     private ActivatableNotificationView getFirstPinnedHeadsUp() {
@@ -3206,6 +3234,10 @@
         }
     }
 
+    public boolean isExpanded() {
+        return mIsExpanded;
+    }
+
     /**
      * A listener that is notified when some child locations might have changed.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index f6959f0..e87b363 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -39,17 +39,14 @@
     private static final String LOG_TAG = "StackScrollAlgorithm";
 
     private static final int MAX_ITEMS_IN_BOTTOM_STACK = 3;
-    private static final int MAX_ITEMS_IN_TOP_STACK = 3;
 
     private int mPaddingBetweenElements;
     private int mIncreasedPaddingBetweenElements;
     private int mCollapsedSize;
-    private int mTopStackPeekSize;
     private int mBottomStackPeekSize;
     private int mZDistanceBetweenElements;
     private int mZBasicHeight;
 
-    private StackIndentationFunctor mTopStackIndentationFunctor;
     private StackIndentationFunctor mBottomStackIndentationFunctor;
 
     private StackScrollAlgorithmState mTempAlgorithmState = new StackScrollAlgorithmState();
@@ -58,12 +55,8 @@
     private boolean mIsExpanded;
     private ExpandableView mFirstChildWhileExpanding;
     private boolean mExpandedOnStart;
-    private int mTopStackTotalSize;
     private int mBottomStackSlowDownLength;
-    private int mTopStackSlowDownLength;
     private int mCollapseSecondCardPadding;
-    private ExpandableView mFirstChild;
-    private int mFirstChildMinHeight;
 
     public StackScrollAlgorithm(Context context) {
         initView(context);
@@ -71,22 +64,6 @@
 
     public void initView(Context context) {
         initConstants(context);
-        updatePadding();
-    }
-
-    private void updatePadding() {
-        mTopStackTotalSize = mTopStackSlowDownLength + mPaddingBetweenElements
-                + mTopStackPeekSize;
-        mTopStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
-                MAX_ITEMS_IN_TOP_STACK,
-                mTopStackPeekSize,
-                mTopStackTotalSize - mTopStackPeekSize,
-                0.5f);
-        mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
-                MAX_ITEMS_IN_BOTTOM_STACK,
-                mBottomStackPeekSize,
-                getBottomStackSlowDownLength(),
-                0.5f);
     }
 
     public int getBottomStackSlowDownLength() {
@@ -100,8 +77,6 @@
                 .getDimensionPixelSize(R.dimen.notification_divider_height_increased);
         mCollapsedSize = context.getResources()
                 .getDimensionPixelSize(R.dimen.notification_min_height);
-        mTopStackPeekSize = context.getResources()
-                .getDimensionPixelSize(R.dimen.top_stack_peek_amount);
         mBottomStackPeekSize = context.getResources()
                 .getDimensionPixelSize(R.dimen.bottom_stack_peek_amount);
         mZDistanceBetweenElements = Math.max(1, context.getResources()
@@ -109,10 +84,13 @@
         mZBasicHeight = (MAX_ITEMS_IN_BOTTOM_STACK + 1) * mZDistanceBetweenElements;
         mBottomStackSlowDownLength = context.getResources()
                 .getDimensionPixelSize(R.dimen.bottom_stack_slow_down_length);
-        mTopStackSlowDownLength = context.getResources()
-                .getDimensionPixelSize(R.dimen.top_stack_slow_down_length);
         mCollapseSecondCardPadding = context.getResources().getDimensionPixelSize(
                 R.dimen.notification_collapse_second_card_padding);
+        mBottomStackIndentationFunctor = new PiecewiseLinearIndentationFunctor(
+                MAX_ITEMS_IN_BOTTOM_STACK,
+                mBottomStackPeekSize,
+                getBottomStackSlowDownLength(),
+                0.5f);
     }
 
     public void getStackScrollState(AmbientState ambientState, StackScrollState resultState) {
@@ -123,32 +101,13 @@
         // First we reset the view states to their default values.
         resultState.resetViewStates();
 
-        algorithmState.itemsInTopStack = 0.0f;
-        algorithmState.partialInTop = 0.0f;
-        algorithmState.lastTopStackIndex = 0;
-        algorithmState.scrolledPixelsTop = 0;
-        algorithmState.itemsInBottomStack = 0.0f;
-        algorithmState.partialInBottom = 0.0f;
-        mFirstChildMinHeight = mFirstChild == null ? 0 : mFirstChild.getMinHeight();
-        float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
+        initAlgorithmState(resultState, algorithmState, ambientState);
 
-        int scrollY = ambientState.getScrollY();
-
-        // Due to the overScroller, the stackscroller can have negative scroll state. This is
-        // already accounted for by the top padding and doesn't need an additional adaption
-        scrollY = Math.max(0, scrollY);
-        algorithmState.scrollY = (int) (scrollY + mFirstChildMinHeight + bottomOverScroll);
-
-        initAlgorithmState(resultState, algorithmState);
-
-        // Phase 1:
-        findNumberOfItemsInTopStackAndUpdateState(resultState, algorithmState, ambientState);
-
-        // Phase 2:
         updatePositionsForState(resultState, algorithmState, ambientState);
 
-        // Phase 3:
-        updateZValuesForState(resultState, algorithmState);
+        updateZValuesForState(resultState, algorithmState, ambientState);
+
+        updateHeadsUpStates(resultState, algorithmState, ambientState);
 
         handleDraggedViews(ambientState, resultState, algorithmState);
         updateDimmedActivatedHideSensitive(ambientState, resultState, algorithmState);
@@ -185,6 +144,7 @@
     private void updateClipping(StackScrollState resultState,
             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
         boolean dismissAllInProgress = ambientState.isDismissAllInProgress();
+        float drawStart = ambientState.getTopPadding() + ambientState.getStackTranslation();
         float previousNotificationEnd = 0;
         float previousNotificationStart = 0;
         boolean previousNotificationIsSwiped = false;
@@ -192,6 +152,10 @@
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = algorithmState.visibleChildren.get(i);
             StackViewState state = resultState.getViewStateForView(child);
+            if (!child.mustStayOnScreen()) {
+                previousNotificationEnd = Math.max(drawStart, previousNotificationEnd);
+                previousNotificationStart = Math.max(drawStart, previousNotificationStart);
+            }
             float newYTranslation = state.yTranslation;
             float newHeight = state.height;
             // apply clipping and shadow
@@ -222,7 +186,7 @@
                 } else {
                     previousNotificationIsSwiped = ambientState.getDraggedViews().contains(child);
                     previousNotificationEnd = newNotificationEnd;
-                    previousNotificationStart = newYTranslation + state.clipTopAmount;
+                    previousNotificationStart =newYTranslation + state.clipTopAmount;
                 }
             }
         }
@@ -314,8 +278,20 @@
     /**
      * Initialize the algorithm state like updating the visible children.
      */
-    private void initAlgorithmState(StackScrollState resultState,
-            StackScrollAlgorithmState state) {
+    private void initAlgorithmState(StackScrollState resultState, StackScrollAlgorithmState state,
+            AmbientState ambientState) {
+        state.itemsInBottomStack = 0.0f;
+        state.partialInBottom = 0.0f;
+        float bottomOverScroll = ambientState.getOverScrollAmount(false /* onTop */);
+
+        int scrollY = ambientState.getScrollY();
+
+        // Due to the overScroller, the stackscroller can have negative scroll state. This is
+        // already accounted for by the top padding and doesn't need an additional adaption
+        scrollY = Math.max(0, scrollY);
+        state.scrollY = (int) (scrollY + bottomOverScroll);
+
+        //now init the visible children and update paddings
         ViewGroup hostView = resultState.getHostView();
         int childCount = hostView.getChildCount();
         state.visibleChildren.clear();
@@ -383,15 +359,9 @@
         float bottomStackStart = bottomPeekStart - mBottomStackSlowDownLength;
 
         // The y coordinate of the current child.
-        float currentYPosition = 0.0f;
-
-        // How far in is the element currently transitioning into the bottom stack.
-        float yPositionInScrollView = 0.0f;
+        float currentYPosition = -algorithmState.scrollY;
 
         int childCount = algorithmState.visibleChildren.size();
-        int numberOfElementsCompletelyIn = algorithmState.partialInTop == 1.0f
-                ? algorithmState.lastTopStackIndex
-                : (int) algorithmState.itemsInTopStack;
         int paddingAfterChild;
         for (int i = 0; i < childCount; i++) {
             ExpandableView child = algorithmState.visibleChildren.get(i);
@@ -400,47 +370,16 @@
             paddingAfterChild = getPaddingAfterChild(algorithmState, child);
             int childHeight = getMaxAllowedChildHeight(child);
             int minHeight = child.getMinHeight();
-            float yPositionInScrollViewAfterElement = yPositionInScrollView
-                    + childHeight
-                    + paddingAfterChild;
-            float scrollOffset = yPositionInScrollView - algorithmState.scrollY +
-                    mFirstChildMinHeight;
-
-            if (i == algorithmState.lastTopStackIndex + 1) {
-                // Normally the position of this child is the position in the regular scrollview,
-                // but if the two stacks are very close to each other,
-                // then have have to push it even more upwards to the position of the bottom
-                // stack start.
-                currentYPosition = Math.min(scrollOffset, bottomStackStart);
-            }
             childViewState.yTranslation = currentYPosition;
+            if (i == 0) {
+                updateFirstChildHeight(child, childViewState, childHeight, ambientState);
+            }
 
             // The y position after this element
             float nextYPosition = currentYPosition + childHeight +
                     paddingAfterChild;
-
-            if (i <= algorithmState.lastTopStackIndex) {
+            if (nextYPosition >= bottomStackStart) {
                 // Case 1:
-                // We are in the top Stack
-                updateStateForTopStackChild(algorithmState, child,
-                        numberOfElementsCompletelyIn, i, childHeight, childViewState, scrollOffset);
-                clampPositionToTopStackEnd(childViewState, childHeight);
-
-                // check if we are overlapping with the bottom stack
-                if (childViewState.yTranslation + childHeight + paddingAfterChild
-                        >= bottomStackStart && !mIsExpansionChanging && i != 0) {
-                    // we just collapse this element slightly
-                    int newSize = (int) Math.max(bottomStackStart - paddingAfterChild -
-                            childViewState.yTranslation, minHeight);
-                    childViewState.height = newSize;
-                    updateStateForChildTransitioningInBottom(algorithmState, bottomStackStart,
-                            child, childViewState.yTranslation, childViewState,
-                            childHeight);
-                }
-                clampPositionToBottomStackStart(childViewState, childViewState.height,
-                        minHeight, ambientState);
-            } else if (nextYPosition >= bottomStackStart) {
-                // Case 2:
                 // We are in the bottom stack.
                 if (currentYPosition >= bottomStackStart) {
                     // According to the regular scroll view we are fully translated out of the
@@ -455,36 +394,30 @@
                             childViewState, childHeight);
                 }
             } else {
-                // Case 3:
+                // Case 2:
                 // We are in the regular scroll area.
                 childViewState.location = StackViewState.LOCATION_MAIN_AREA;
-                clampYTranslation(childViewState, childHeight, ambientState);
+                clampPositionToBottomStackStart(childViewState, childViewState.height, childHeight,
+                        ambientState);
             }
 
-            // The first card is always rendered.
-            if (i == 0) {
-                childViewState.hidden = false;
-                childViewState.shadowAlpha = 1.0f;
-                childViewState.yTranslation = Math.max(
-                        mFirstChildMinHeight - algorithmState.scrollY, 0);
-                if (childViewState.yTranslation + childViewState.height
-                        > bottomPeekStart - mCollapseSecondCardPadding) {
-                    childViewState.height = (int) Math.max(
-                            bottomPeekStart - mCollapseSecondCardPadding
-                                    - childViewState.yTranslation, mFirstChildMinHeight);
-                }
-                childViewState.location = StackViewState.LOCATION_FIRST_CARD;
+            if (i == 0 && ambientState.getScrollY() <= 0) {
+                // The first card can get into the bottom stack if it's the only one
+                // on the lockscreen which pushes it up. Let's make sure that doesn't happen and
+                // it stays at the top
+                childViewState.yTranslation = Math.max(0, childViewState.yTranslation);
+            }
+            currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
+            if (currentYPosition <= 0) {
+                childViewState.location = StackViewState.LOCATION_HIDDEN_TOP;
             }
             if (childViewState.location == StackViewState.LOCATION_UNKNOWN) {
                 Log.wtf(LOG_TAG, "Failed to assign location for child " + i);
             }
-            currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
-            yPositionInScrollView = yPositionInScrollViewAfterElement;
 
             childViewState.yTranslation += ambientState.getTopPadding()
                     + ambientState.getStackTranslation();
         }
-        updateHeadsUpStates(resultState, algorithmState, ambientState);
     }
 
     private int getPaddingAfterChild(StackScrollAlgorithmState algorithmState,
@@ -506,24 +439,27 @@
             ExpandableNotificationRow row = (ExpandableNotificationRow) child;
             if (!row.isHeadsUp()) {
                 break;
-            } else if (topHeadsUpEntry == null) {
-                topHeadsUpEntry = row;
             }
             StackViewState childState = resultState.getViewStateForView(row);
+            if (topHeadsUpEntry == null) {
+                topHeadsUpEntry = row;
+                childState.location = StackViewState.LOCATION_FIRST_HUN;
+            }
             boolean isTopEntry = topHeadsUpEntry == row;
+            float unmodifiedEndLocation = childState.yTranslation + childState.height;
             if (mIsExpanded) {
-                // Ensure that the heads up is always visible even when scrolled off from the bottom
-                float bottomPosition = ambientState.getMaxHeadsUpTranslation() - childState.height;
-                childState.yTranslation = Math.min(childState.yTranslation,
-                        bottomPosition);
+                // Ensure that the heads up is always visible even when scrolled off
+                clampHunToTop(ambientState, row, childState);
+                clampHunToMaxTranslation(ambientState, row, childState);
             }
             if (row.isPinned()) {
                 childState.yTranslation = Math.max(childState.yTranslation, 0);
                 childState.height = Math.max(row.getIntrinsicHeight(), childState.height);
-                if (!isTopEntry) {
+                StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
+                if (!isTopEntry && (!mIsExpanded
+                        || unmodifiedEndLocation < topState.yTranslation + topState.height)) {
                     // Ensure that a headsUp doesn't vertically extend further than the heads-up at
                     // the top most z-position
-                    StackViewState topState = resultState.getViewStateForView(topHeadsUpEntry);
                     childState.height = row.getIntrinsicHeight();
                     childState.yTranslation = topState.yTranslation + topState.height
                             - childState.height;
@@ -532,17 +468,23 @@
         }
     }
 
-    /**
-     * Clamp the yTranslation both up and down to valid positions.
-     *
-     * @param childViewState the view state of the child
-     * @param minHeight the minimum height of this child
-     */
-    private void clampYTranslation(StackViewState childViewState, int minHeight,
-            AmbientState ambientState) {
-        clampPositionToBottomStackStart(childViewState, childViewState.height, minHeight,
-                ambientState);
-        clampPositionToTopStackEnd(childViewState, childViewState.height);
+    private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
+            StackViewState childState) {
+        float newTranslation = Math.max(ambientState.getTopPadding()
+                + ambientState.getStackTranslation(), childState.yTranslation);
+        childState.height = (int) Math.max(childState.height - (newTranslation
+                - childState.yTranslation), row.getMinHeight());
+        childState.yTranslation = newTranslation;
+    }
+
+    private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
+            StackViewState childState) {
+        float newTranslation;
+        float bottomPosition = ambientState.getMaxHeadsUpTranslation() - row.getMinHeight();
+        newTranslation = Math.min(childState.yTranslation, bottomPosition);
+        childState.height = (int) Math.max(childState.height
+                - (childState.yTranslation - newTranslation), row.getMinHeight());
+        childState.yTranslation = newTranslation;
     }
 
     /**
@@ -569,19 +511,6 @@
         }
     }
 
-    /**
-     * Clamp the yTranslation of the child up such that its end is at lest on the end of the top
-     * stack.
-     *
-     * @param childViewState the view state of the child
-     * @param childHeight the height of this child
-     */
-    private void clampPositionToTopStackEnd(StackViewState childViewState,
-            int childHeight) {
-        childViewState.yTranslation = Math.max(childViewState.yTranslation,
-                mFirstChildMinHeight - childHeight);
-    }
-
     private int getMaxAllowedChildHeight(View child) {
         if (child instanceof ExpandableView) {
             ExpandableView expandableView = (ExpandableView) child;
@@ -611,9 +540,6 @@
         }
         childViewState.yTranslation = transitioningPositionStart + offset - newHeight
                 - getPaddingAfterChild(algorithmState, child);
-
-        // We want at least to be at the end of the top stack when collapsing
-        clampPositionToTopStackEnd(childViewState, newHeight);
         childViewState.location = StackViewState.LOCATION_MAIN_AREA;
     }
 
@@ -642,177 +568,59 @@
         }
         childViewState.height = minHeight;
         childViewState.yTranslation = currentYPosition - minHeight;
-        clampPositionToTopStackEnd(childViewState, minHeight);
     }
 
-    private void updateStateForTopStackChild(StackScrollAlgorithmState algorithmState,
-            ExpandableView child, int numberOfElementsCompletelyIn, int i, int childHeight,
-            StackViewState childViewState, float scrollOffset) {
-
-
-        // First we calculate the index relative to the current stack window of size at most
-        // {@link #MAX_ITEMS_IN_TOP_STACK}
-        int paddedIndex = i - 1
-                - Math.max(numberOfElementsCompletelyIn - MAX_ITEMS_IN_TOP_STACK, 0);
-        if (paddedIndex >= 0) {
-
-            // We are currently visually entering the top stack
-            float distanceToStack = (childHeight + getPaddingAfterChild(algorithmState, child))
-                    - algorithmState.scrolledPixelsTop;
-            if (i == algorithmState.lastTopStackIndex
-                    && distanceToStack > (mTopStackTotalSize
-                    + getPaddingAfterChild(algorithmState, child))) {
-
-                // Child is currently translating into stack but not yet inside slow down zone.
-                // Handle it like the regular scrollview.
-                childViewState.yTranslation = scrollOffset;
-            } else {
-                // Apply stacking logic.
-                float numItemsBefore;
-                if (i == algorithmState.lastTopStackIndex) {
-                    numItemsBefore = 1.0f
-                            - (distanceToStack / (mTopStackTotalSize
-                            + getPaddingAfterChild(algorithmState, child)));
-                } else {
-                    numItemsBefore = algorithmState.itemsInTopStack - i;
-                }
-                // The end position of the current child
-                float currentChildEndY = mFirstChildMinHeight + mTopStackTotalSize
-                        - mTopStackIndentationFunctor.getValue(numItemsBefore);
-                childViewState.yTranslation = currentChildEndY - childHeight;
-            }
-            childViewState.location = StackViewState.LOCATION_TOP_STACK_PEEKING;
-        } else {
-            if (paddedIndex == -1) {
-                childViewState.shadowAlpha = 1.0f - algorithmState.partialInTop;
-            } else {
-                // We are hidden behind the top card and faded out, so we can hide ourselves.
-                childViewState.hidden = true;
-                childViewState.shadowAlpha = 0.0f;
-            }
-            childViewState.yTranslation = mFirstChildMinHeight - childHeight;
-            childViewState.location = StackViewState.LOCATION_TOP_STACK_HIDDEN;
-        }
-
-
-    }
 
     /**
-     * Find the number of items in the top stack and update the result state if needed.
+     * Update the height of the first child i.e clamp it to the bottom stack
      *
-     * @param resultState The result state to update if a height change of an child occurs
-     * @param algorithmState The state in which the current pass of the algorithm is currently in
+     *
+
+     * @param child the child to update
+     * @param childViewState the viewstate of the child
+     * @param childHeight the height of the child
+     * @param ambientState The ambient state of the algorithm
      */
-    private void findNumberOfItemsInTopStackAndUpdateState(StackScrollState resultState,
-            StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
+    private void updateFirstChildHeight(ExpandableView child, StackViewState childViewState,
+            int childHeight, AmbientState ambientState) {
 
-        // The y Position if the element would be in a regular scrollView
-        float yPositionInScrollView = 0.0f;
-        int childCount = algorithmState.visibleChildren.size();
-        // find the number of elements in the top stack.
-        for (int i = 0; i < childCount; i++) {
-            ExpandableView child = algorithmState.visibleChildren.get(i);
-            StackViewState childViewState = resultState.getViewStateForView(child);
-            int childHeight = getMaxAllowedChildHeight(child);
-            int paddingAfterChild = getPaddingAfterChild(algorithmState, child);
-            float yPositionInScrollViewAfterElement = yPositionInScrollView
-                    + childHeight
-                    + paddingAfterChild;
-            if (yPositionInScrollView < algorithmState.scrollY) {
-                if (i == 0 && algorithmState.scrollY <= mFirstChildMinHeight) {
-
-                    // The starting position of the bottom stack peek
-                    int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
-                            mCollapseSecondCardPadding;
-                    // Collapse and expand the first child while the shade is being expanded
-                    float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding
-                            ? mFirstChildMaxHeight
-                            : childHeight;
-                    childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
-                            mFirstChildMinHeight);
-                    algorithmState.itemsInTopStack = 1.0f;
-
-                } else if (yPositionInScrollViewAfterElement < algorithmState.scrollY) {
-                    // According to the regular scroll view we are fully off screen
-                    algorithmState.itemsInTopStack += 1.0f;
-                    if (i == 0) {
-                        childViewState.height = child.getMinHeight();
-                    }
-                } else {
-                    // According to the regular scroll view we are partially off screen
-
-                    // How much did we scroll into this child
-                    algorithmState.scrolledPixelsTop = algorithmState.scrollY
-                            - yPositionInScrollView;
-                    algorithmState.partialInTop = (algorithmState.scrolledPixelsTop) / (childHeight
-                            + paddingAfterChild);
-
-                    // Our element can be expanded, so this can get negative
-                    algorithmState.partialInTop = Math.max(0.0f, algorithmState.partialInTop);
-                    algorithmState.itemsInTopStack += algorithmState.partialInTop;
-
-                    if (i == 0) {
-                        // If it is expanded we have to collapse it to a new size
-                        float newSize = yPositionInScrollViewAfterElement
-                                - paddingAfterChild
-                                - algorithmState.scrollY + mFirstChildMinHeight;
-                        newSize = Math.max(mFirstChildMinHeight, newSize);
-                        algorithmState.itemsInTopStack = 1.0f;
-                        childViewState.height = (int) newSize;
-                    }
-                    algorithmState.lastTopStackIndex = i;
-                    break;
-                }
-            } else {
-                algorithmState.lastTopStackIndex = i - 1;
-                // We are already past the stack so we can end the loop
-                break;
-            }
-            yPositionInScrollView = yPositionInScrollViewAfterElement;
-        }
+            // The starting position of the bottom stack peek
+            int bottomPeekStart = ambientState.getInnerHeight() - mBottomStackPeekSize -
+                    mCollapseSecondCardPadding + ambientState.getScrollY();
+            // Collapse and expand the first child while the shade is being expanded
+            float maxHeight = mIsExpansionChanging && child == mFirstChildWhileExpanding
+                    ? mFirstChildMaxHeight
+                    : childHeight;
+            childViewState.height = (int) Math.max(Math.min(bottomPeekStart, maxHeight),
+                    child.getMinHeight());
     }
 
     /**
      * Calculate the Z positions for all children based on the number of items in both stacks and
      * save it in the resultState
-     *
-     * @param resultState The result state to update the zTranslation values
+     *  @param resultState The result state to update the zTranslation values
      * @param algorithmState The state in which the current pass of the algorithm is currently in
+     * @param ambientState The ambient state of the algorithm
      */
     private void updateZValuesForState(StackScrollState resultState,
-            StackScrollAlgorithmState algorithmState) {
+            StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
         int childCount = algorithmState.visibleChildren.size();
-        for (int i = 0; i < childCount; i++) {
-            View child = algorithmState.visibleChildren.get(i);
+        int childrenOnTop = 0;
+        for (int i = childCount - 1; i >= 0; i--) {
+            ExpandableView child = algorithmState.visibleChildren.get(i);
             StackViewState childViewState = resultState.getViewStateForView(child);
-            if (i < algorithmState.itemsInTopStack) {
-                float stackIndex = algorithmState.itemsInTopStack - i;
-
-                // Ensure that the topmost item is a little bit higher than the rest when fully
-                // scrolled, to avoid drawing errors when swiping it out
-                float max = MAX_ITEMS_IN_TOP_STACK + (i == 0 ? 2.5f : 2);
-                stackIndex = Math.min(stackIndex, max);
-                if (i == 0 && algorithmState.itemsInTopStack < 2.0f) {
-
-                    // We only have the top item and an additional item in the top stack,
-                    // Interpolate the index from 0 to 2 while the second item is
-                    // translating in.
-                    stackIndex -= 1.0f;
-                    if (algorithmState.scrollY > mFirstChildMinHeight) {
-
-                        // Since there is a shadow treshhold, we cant just interpolate from 0 to
-                        // 2 but we interpolate from 0.1f to 2.0f when scrolled in. The jump in
-                        // height will not be noticable since we have padding in between.
-                        stackIndex = 0.1f + stackIndex * 1.9f;
-                    }
-                }
-                childViewState.zTranslation = mZBasicHeight
-                        + stackIndex * mZDistanceBetweenElements;
-            } else if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
+            if (i > (childCount - 1 - algorithmState.itemsInBottomStack)) {
+                // We are in the bottom stack
                 float numItemsAbove = i - (childCount - 1 - algorithmState.itemsInBottomStack);
-                float translationZ = mZBasicHeight
+                childViewState.zTranslation = mZBasicHeight
                         - numItemsAbove * mZDistanceBetweenElements;
-                childViewState.zTranslation = translationZ;
+            } else if (child.mustStayOnScreen()
+                    && childViewState.yTranslation < ambientState.getTopPadding()
+                    + ambientState.getStackTranslation()) {
+                // TODO; do this more cleanly
+                childrenOnTop++;
+                childViewState.zTranslation = mZBasicHeight
+                        + childrenOnTop * mZDistanceBetweenElements;
             } else {
                 childViewState.zTranslation = mZBasicHeight;
             }
@@ -897,7 +705,6 @@
     }
 
     public void notifyChildrenChanged(final NotificationStackScrollLayout hostView) {
-        mFirstChild = hostView.getFirstChildNotGone();
         if (mIsExpansionChanging) {
             hostView.post(new Runnable() {
                 @Override
@@ -922,26 +729,6 @@
         public int scrollY;
 
         /**
-         *  The quantity of items which are in the top stack.
-         */
-        public float itemsInTopStack;
-
-        /**
-         * how far in is the element currently transitioning into the top stack
-         */
-        public float partialInTop;
-
-        /**
-         * The number of pixels the last child in the top stack has scrolled in to the stack
-         */
-        public float scrolledPixelsTop;
-
-        /**
-         * The last item index which is in the top stack.
-         */
-        public int lastTopStackIndex;
-
-        /**
          * The quantity of items which are in the bottom stack.
          */
         public float itemsInBottomStack;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
index 05fa27d..fa15195 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackViewState.java
@@ -24,12 +24,11 @@
     // These are flags such that we can create masks for filtering.
 
     public static final int LOCATION_UNKNOWN = 0x00;
-    public static final int LOCATION_FIRST_CARD = 0x01;
-    public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
-    public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
-    public static final int LOCATION_MAIN_AREA = 0x08;
-    public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
-    public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
+    public static final int LOCATION_FIRST_HUN = 0x01;
+    public static final int LOCATION_HIDDEN_TOP = 0x02;
+    public static final int LOCATION_MAIN_AREA = 0x04;
+    public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x08;
+    public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10;
     /** The view isn't layouted at all. */
     public static final int LOCATION_GONE = 0x40;
 
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
index 3e47d85..3c30410 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipManager.java
@@ -28,6 +28,7 @@
 import android.content.IntentFilter;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.os.Debug;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
@@ -53,13 +54,17 @@
 
     private static final int MAX_RUNNING_TASKS_COUNT = 10;
 
-    private static final int STATE_NO_PIP = 0;
-    private static final int STATE_PIP_OVERLAY = 1;
-    private static final int STATE_PIP_MENU = 2;
+    public static final int STATE_NO_PIP = 0;
+    public static final int STATE_PIP_OVERLAY = 1;
+    public static final int STATE_PIP_MENU = 2;
 
     private static final int TASK_ID_NO_PIP = -1;
     private static final int INVALID_RESOURCE_TYPE = -1;
 
+    public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH = 0x1;
+    public static final int SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH = 0x2;
+    private int mSuspendPipResizingReason;
+
     private Context mContext;
     private IActivityManager mActivityManager;
     private int mState = STATE_NO_PIP;
@@ -87,7 +92,8 @@
             }
             if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
             mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
-            showPipOverlay(false);
+            // Set state to overlay so we show it when the pinned stack animation ends.
+            mState = STATE_PIP_OVERLAY;
             launchPipOnboardingActivityIfNeeded();
         }
     };
@@ -105,6 +111,23 @@
             movePipToFullscreen();
         }
     };
+    private final Runnable mOnPinnedStackAnimationEnded = new Runnable() {
+        @Override
+        public void run() {
+            if (mState == STATE_PIP_OVERLAY) {
+                showPipOverlay();
+            } else if (mState == STATE_PIP_MENU) {
+                showPipMenu();
+            }
+        }
+    };
+
+    private final Runnable mResizePinnedStackRunnable = new Runnable() {
+        @Override
+        public void run() {
+            resizePinnedStack(mState);
+        }
+    };
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -164,7 +187,7 @@
         if (!hasPipTasks()) {
             startPip();
         } else if (mState == STATE_PIP_OVERLAY) {
-            showPipMenu();
+            resizePinnedStack(STATE_PIP_MENU);
         }
     }
 
@@ -210,11 +233,7 @@
         for (int i = mListeners.size() - 1; i >= 0; --i) {
             mListeners.get(i).onMoveToFullscreen();
         }
-        try {
-            mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true);
-        } catch (RemoteException e) {
-            Log.e(TAG, "moveTasksToFullscreenStack failed", e);
-        }
+        resizePinnedStack(mState);
     }
 
     /**
@@ -222,25 +241,83 @@
      * stack to the default PIP bound {@link com.android.internal.R.string
      * .config_defaultPictureInPictureBounds}.
      */
-    public void showPipOverlay(boolean resizeStack) {
+    private void showPipOverlay() {
         if (DEBUG) Log.d(TAG, "showPipOverlay()");
         mState = STATE_PIP_OVERLAY;
         Intent intent = new Intent(mContext, PipOverlayActivity.class);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchStackId(PINNED_STACK_ID);
-        if (resizeStack) {
-            options.setLaunchBounds(mPipBound);
-        }
         mContext.startActivity(intent, options.toBundle());
     }
 
     /**
+     * Suspends resizing operation on the Pip until {@link #resumePipResizing} is called
+     * @param reason The reason for suspending resizing operations on the Pip.
+     */
+    public void suspendPipResizing(int reason) {
+        if (DEBUG) Log.d(TAG,
+                "suspendPipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
+        mSuspendPipResizingReason |= reason;
+    }
+
+    /**
+     * Resumes resizing operation on the Pip that was previously suspended.
+     * @param reason The reason resizing operations on the Pip was suspended.
+     */
+    public void resumePipResizing(int reason) {
+        if ((mSuspendPipResizingReason & reason) == 0) {
+            return;
+        }
+        if (DEBUG) Log.d(TAG,
+                "resumePipResizing() reason=" + reason + " callers=" + Debug.getCallers(2));
+        mSuspendPipResizingReason &= ~reason;
+        mHandler.post(mResizePinnedStackRunnable);
+    }
+
+    /**
+     * Resize the Pip to the appropriate size for the input state.
+     * @param state In Pip state also used to determine the new size for the Pip.
+     */
+    public void resizePinnedStack(int state) {
+        if (DEBUG) Log.d(TAG, "resizePinnedStack() state=" + state);
+        mState = state;
+        Rect bounds;
+        for (int i = mListeners.size() - 1; i >= 0; --i) {
+            mListeners.get(i).onPipResizeAboutToStart();
+        }
+        switch (mState) {
+            case STATE_PIP_MENU:
+                bounds = mMenuModePipBound;
+                break;
+            case STATE_NO_PIP:
+                bounds = null;
+                break;
+            default:
+                bounds = mPipBound;
+                break;
+        }
+
+        if (mSuspendPipResizingReason != 0) {
+            if (DEBUG) Log.d(TAG,
+                    "resizePinnedStack() deferring mSuspendPipResizingReason=" +
+                            mSuspendPipResizingReason);
+            return;
+        }
+
+        try {
+            mActivityManager.resizeStack(PINNED_STACK_ID, bounds, true, true, true);
+        } catch (RemoteException e) {
+            Log.e(TAG, "showPipMenu failed", e);
+        }
+    }
+
+    /**
      * Shows PIP menu UI by launching {@link PipMenuActivity}. It also locates the pinned
      * stack to the centered PIP bound {@link com.android.internal.R.string
      * .config_centeredPictureInPictureBounds}.
      */
-    public void showPipMenu() {
+    private void showPipMenu() {
         if (DEBUG) Log.d(TAG, "showPipMenu()");
         mState = STATE_PIP_MENU;
         for (int i = mListeners.size() - 1; i >= 0; --i) {
@@ -250,20 +327,13 @@
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchStackId(PINNED_STACK_ID);
-        options.setLaunchBounds(mMenuModePipBound);
         mContext.startActivity(intent, options.toBundle());
     }
 
-    /**
-     * Adds {@link Listener}.
-     */
     public void addListener(Listener listener) {
         mListeners.add(listener);
     }
 
-    /**
-     * Removes {@link Listener}.
-     */
     public void removeListener(Listener listener) {
         mListeners.remove(listener);
     }
@@ -338,32 +408,36 @@
         @Override
         public void onActivityPinned()  throws RemoteException {
             // Post the message back to the UI thread.
+            if (DEBUG) Log.d(TAG, "onActivityPinned()");
             mHandler.post(mOnActivityPinnedRunnable);
         }
 
         @Override
         public void onPinnedActivityRestartAttempt() {
             // Post the message back to the UI thread.
+            if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
             mHandler.post(mOnPinnedActivityRestartAttempt);
         }
+
+        @Override
+        public void onPinnedStackAnimationEnded() {
+            if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
+            mHandler.post(mOnPinnedStackAnimationEnded);
+        }
     }
 
     /**
      * A listener interface to receive notification on changes in PIP.
      */
     public interface Listener {
-        /**
-         * Invoked when a PIPed activity is closed.
-         */
+        /** Invoked when a PIPed activity is closed. */
         void onPipActivityClosed();
-        /**
-         * Invoked when the PIP menu gets shown.
-         */
+        /** Invoked when the PIP menu gets shown. */
         void onShowPipMenu();
-        /**
-         * Invoked when the PIPed activity is returned back to the fullscreen.
-         */
+        /** Invoked when the PIPed activity is returned back to the fullscreen. */
         void onMoveToFullscreen();
+        /** Invoked when we are above to start resizing the Pip. */
+        void onPipResizeAboutToStart();
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
index 15c55f5..7e229d4 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipMenuActivity.java
@@ -54,7 +54,7 @@
         findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
-                mPipManager.showPipOverlay(true);
+                mPipManager.resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
                 finish();
             }
         });
@@ -62,13 +62,15 @@
 
     @Override
     protected void onDestroy() {
-        mPipManager.removeListener(this);
         super.onDestroy();
+        mPipManager.removeListener(this);
+        mPipManager.resumePipResizing(
+                PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
     }
 
     @Override
     public void onBackPressed() {
-        mPipManager.showPipOverlay(true);
+        mPipManager.resizePinnedStack(PipManager.STATE_PIP_OVERLAY);
         finish();
     }
 
@@ -84,4 +86,11 @@
     public void onMoveToFullscreen() {
         finish();
     }
+
+    @Override
+    public void onPipResizeAboutToStart() {
+        finish();
+        mPipManager.suspendPipResizing(
+                PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_MENU_ACTIVITY_FINISH);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
index a0b913a..6f71c92 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOnboardingActivity.java
@@ -62,4 +62,8 @@
     public void onMoveToFullscreen() {
         finish();
     }
+
+    @Override
+    public void onPipResizeAboutToStart() {
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
index bc59a8c..b407935 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/pip/PipOverlayActivity.java
@@ -19,8 +19,8 @@
 import android.app.Activity;
 import android.os.Bundle;
 import android.os.Handler;
-import android.view.View;
 
+import android.view.View;
 import com.android.systemui.R;
 
 /**
@@ -30,25 +30,37 @@
     private static final String TAG = "PipOverlayActivity";
     private static final boolean DEBUG = false;
 
-    private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 2000;
+    private static final long SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS = 4000;
 
     private final PipManager mPipManager = PipManager.getInstance();
     private final Handler mHandler = new Handler();
+    private View mGuideOverlayView;
+    private final Runnable mHideGuideOverlayRunnable = new Runnable() {
+        public void run() {
+            mGuideOverlayView.setVisibility(View.INVISIBLE);
+        }
+    };
 
     @Override
     protected void onCreate(Bundle bundle) {
         super.onCreate(bundle);
         setContentView(R.layout.tv_pip_overlay);
+        mGuideOverlayView = findViewById(R.id.guide_overlay);
         mPipManager.addListener(this);
-        final View overlayView = findViewById(R.id.guide_overlay);
-        // TODO: apply animation
-        overlayView.setVisibility(View.VISIBLE);
-        mHandler.postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                overlayView.setVisibility(View.INVISIBLE);
-            }
-        }, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mHandler.removeCallbacks(mHideGuideOverlayRunnable);
+        mHandler.postDelayed(mHideGuideOverlayRunnable, SHOW_GUIDE_OVERLAY_VIEW_DURATION_MS);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mHandler.removeCallbacks(mHideGuideOverlayRunnable);
+        finish();
     }
 
     @Override
@@ -56,6 +68,8 @@
         super.onDestroy();
         mHandler.removeCallbacksAndMessages(null);
         mPipManager.removeListener(this);
+        mPipManager.resumePipResizing(
+                PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH);
     }
 
     @Override
@@ -72,4 +86,11 @@
     public void onMoveToFullscreen() {
         finish();
     }
+
+    @Override
+    public void onPipResizeAboutToStart() {
+        finish();
+        mPipManager.suspendPipResizing(
+                PipManager.SUSPEND_PIP_RESIZE_REASON_WAITING_FOR_OVERLAY_ACTIVITY_FINISH);
+    }
 }
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 3327ec4..3f3f851 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -318,10 +318,10 @@
     OVERVIEW_HISTORY = 275;
 
     // Logged when the user pages through overview.
-    ACTION_OVERVIEW_PAGE = 276;
+    OVERVIEW_PAGE = 276;
 
     // Logged when the user launches a task from overview.
-    ACTION_OVERVIEW_SELECT = 277;
+    OVERVIEW_SELECT = 277;
 
     // Logged when the user views the emergency info.
     ACTION_VIEW_EMERGENCY_INFO = 278;
@@ -353,5 +353,21 @@
     // Logged when the user undocks a previously docked window by long pressing recents while in
     // docked mode.
     ACTION_WINDOW_UNDOCK_LONGPRESS = 286;
+
+    // Logged when the user scrolls through overview manually
+    OVERVIEW_SCROLL = 287;
+
+    // Logged when the overview times out automatically selecting an app
+    OVERVIEW_SELECT_TIMEOUT = 288;
+
+    // Logged when a user dismisses a task in overview
+    OVERVIEW_DISMISS = 289;
+
+    // Logged when the user modifying the notification importance slider.
+    ACTION_MODIFY_IMPORTANCE_SLIDER = 290;
+
+    // Logged when the user saves a modification to notification importance. Negative numbers
+    // indicate the user lowered the importance; positive means they increased it.
+    ACTION_SAVE_IMPORTANCE = 291;
   }
 }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index c35a73a..4be6833 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -52,7 +52,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Debug;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -654,7 +653,7 @@
             userState.mUiAutomationFlags = flags;
             userState.mIsAccessibilityEnabled = true;
             userState.mInstalledServices.add(accessibilityServiceInfo);
-            if (userState.isUiAutomationSuppressingOtherServices()) {
+            if ((flags & UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES) == 0) {
                 // Set the temporary state.
                 userState.mIsTouchExplorationEnabled = false;
                 userState.mIsEnhancedWebAccessibilityEnabled = false;
diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java
index 6062137..383e25a 100644
--- a/services/core/java/com/android/server/ServiceWatcher.java
+++ b/services/core/java/com/android/server/ServiceWatcher.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -32,13 +33,16 @@
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.util.Log;
+import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.PackageMonitor;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * Find the best Service, and bind to it.
@@ -64,17 +68,21 @@
     private final Runnable mNewServiceWork;
     private final Handler mHandler;
 
-    private Object mLock = new Object();
+    private final Object mLock = new Object();
 
-    // all fields below synchronized on mLock
-    private IBinder mBinder;   // connected service
-    private String mPackageName;  // current best package
-    private int mVersion = Integer.MIN_VALUE;  // current best version
-    /**
-     * Whether the currently-connected service is multiuser-aware. This can change at run-time
-     * when switching from one version of a service to another.
-     */
-    private boolean mIsMultiuser = false;
+    @GuardedBy("mLock")
+    private int mCurrentUserId = UserHandle.USER_SYSTEM;
+
+    @GuardedBy("mLock")
+    private IBinder mBoundService;
+    @GuardedBy("mLock")
+    private ComponentName mBoundComponent;
+    @GuardedBy("mLock")
+    private String mBoundPackageName;
+    @GuardedBy("mLock")
+    private int mBoundVersion = Integer.MIN_VALUE;
+    @GuardedBy("mLock")
+    private int mBoundUserId = UserHandle.USER_NULL;
 
     public static ArrayList<HashSet<Signature>> getSignatureSets(Context context,
             List<String> initialPackageNames) {
@@ -84,7 +92,8 @@
             String pkg = initialPackageNames.get(i);
             try {
                 HashSet<Signature> set = new HashSet<Signature>();
-                Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures;
+                Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.MATCH_SYSTEM_ONLY
+                        | PackageManager.GET_SIGNATURES).signatures;
                 set.addAll(Arrays.asList(sigs));
                 sigSets.add(set);
             } catch (NameNotFoundException e) {
@@ -108,7 +117,7 @@
 
         // Whether to enable service overlay.
         boolean enableOverlay = resources.getBoolean(overlaySwitchResId);
-        ArrayList<String>  initialPackageNames = new ArrayList<String>();
+        ArrayList<String> initialPackageNames = new ArrayList<String>();
         if (enableOverlay) {
             // A list of package names used to create the signatures.
             String[] pkgs = resources.getStringArray(initialPackageNamesResId);
@@ -126,20 +135,32 @@
         mSignatureSets = getSignatureSets(context, initialPackageNames);
     }
 
+    /**
+     * Start this watcher, including binding to the current best match and
+     * re-binding to any better matches down the road.
+     * <p>
+     * Note that if there are no matching encryption-aware services, we may not
+     * bind to a real service until after the current user is unlocked.
+     */
     public boolean start() {
         synchronized (mLock) {
-            if (!bindBestPackageLocked(mServicePackageName)) return false;
+            bindBestPackageLocked(mServicePackageName, false);
         }
 
         // listen for user change
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
         mContext.registerReceiverAsUser(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
+                final String action = intent.getAction();
+                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+                        UserHandle.USER_NULL);
                 if (Intent.ACTION_USER_SWITCHED.equals(action)) {
-                    switchUser();
+                    switchUser(userId);
+                } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
+                    unlockUser(userId);
                 }
             }
         }, UserHandle.ALL, intentFilter, null, mHandler);
@@ -153,30 +174,36 @@
     }
 
     /**
-     * Searches and binds to the best package, or do nothing
-     * if the best package is already bound.
-     * Only checks the named package, or checks all packages if it
-     * is null.
-     * Return true if a new package was found to bind to.
+     * Searches and binds to the best package, or do nothing if the best package
+     * is already bound, unless force rebinding is requested.
+     *
+     * @param justCheckThisPackage Only consider this package, or consider all
+     *            packages if it is {@code null}.
+     * @param forceRebind Force a rebinding to the best package if it's already
+     *            bound.
+     * @return {@code true} if a valid package was found to bind to.
      */
-    private boolean bindBestPackageLocked(String justCheckThisPackage) {
+    private boolean bindBestPackageLocked(String justCheckThisPackage, boolean forceRebind) {
         Intent intent = new Intent(mAction);
         if (justCheckThisPackage != null) {
             intent.setPackage(justCheckThisPackage);
         }
-        List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
-                PackageManager.GET_META_DATA, UserHandle.USER_SYSTEM);
+        final List<ResolveInfo> rInfos = mPm.queryIntentServicesAsUser(intent,
+                PackageManager.GET_META_DATA | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+                mCurrentUserId);
         int bestVersion = Integer.MIN_VALUE;
-        String bestPackage = null;
+        ComponentName bestComponent = null;
         boolean bestIsMultiuser = false;
         if (rInfos != null) {
             for (ResolveInfo rInfo : rInfos) {
-                String packageName = rInfo.serviceInfo.packageName;
+                final ComponentName component = rInfo.serviceInfo.getComponentName();
+                final String packageName = component.getPackageName();
 
                 // check signature
                 try {
                     PackageInfo pInfo;
-                    pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+                    pInfo = mPm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES
+                            | PackageManager.MATCH_DEBUG_TRIAGED_MISSING);
                     if (!isSignatureMatch(pInfo.signatures)) {
                         Log.w(mTag, packageName + " resolves service " + mAction
                                 + ", but has wrong signature, ignoring");
@@ -196,9 +223,9 @@
                     isMultiuser = rInfo.serviceInfo.metaData.getBoolean(EXTRA_SERVICE_IS_MULTIUSER);
                 }
 
-                if (version > mVersion) {
+                if (version > bestVersion) {
                     bestVersion = version;
-                    bestPackage = packageName;
+                    bestComponent = component;
                     bestIsMultiuser = isMultiuser;
                 }
             }
@@ -207,42 +234,53 @@
                 Log.d(mTag, String.format("bindBestPackage for %s : %s found %d, %s", mAction,
                         (justCheckThisPackage == null ? ""
                                 : "(" + justCheckThisPackage + ") "), rInfos.size(),
-                        (bestPackage == null ? "no new best package"
-                                : "new best package: " + bestPackage)));
+                        (bestComponent == null ? "no new best component"
+                                : "new best component: " + bestComponent)));
             }
         } else {
             if (D) Log.d(mTag, "Unable to query intent services for action: " + mAction);
         }
-        if (bestPackage != null) {
-            bindToPackageLocked(bestPackage, bestVersion, bestIsMultiuser);
-            return true;
+
+        if (bestComponent == null) {
+            Slog.w(mTag, "Odd, no component found for service " + mAction);
+            unbindLocked();
+            return false;
         }
-        return false;
+
+        final int userId = bestIsMultiuser ? UserHandle.USER_SYSTEM : mCurrentUserId;
+        final boolean alreadyBound = Objects.equals(bestComponent, mBoundComponent)
+                && bestVersion == mBoundVersion && userId == mBoundUserId;
+        if (forceRebind || !alreadyBound) {
+            unbindLocked();
+            bindToPackageLocked(bestComponent, bestVersion, userId);
+        }
+        return true;
     }
 
     private void unbindLocked() {
-        String pkg;
-        pkg = mPackageName;
-        mPackageName = null;
-        mVersion = Integer.MIN_VALUE;
-        mIsMultiuser = false;
-        if (pkg != null) {
-            if (D) Log.d(mTag, "unbinding " + pkg);
+        ComponentName component;
+        component = mBoundComponent;
+        mBoundComponent = null;
+        mBoundPackageName = null;
+        mBoundVersion = Integer.MIN_VALUE;
+        mBoundUserId = UserHandle.USER_NULL;
+        if (component != null) {
+            if (D) Log.d(mTag, "unbinding " + component);
             mContext.unbindService(this);
         }
     }
 
-    private void bindToPackageLocked(String packageName, int version, boolean isMultiuser) {
-        unbindLocked();
+    private void bindToPackageLocked(ComponentName component, int version, int userId) {
         Intent intent = new Intent(mAction);
-        intent.setPackage(packageName);
-        mPackageName = packageName;
-        mVersion = version;
-        mIsMultiuser = isMultiuser;
-        if (D) Log.d(mTag, "binding " + packageName + " (version " + version + ") ("
-                + (isMultiuser ? "multi" : "single") + "-user)");
-        mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
-                | Context.BIND_NOT_VISIBLE, mIsMultiuser ? UserHandle.SYSTEM : UserHandle.CURRENT);
+        intent.setComponent(component);
+        mBoundComponent = component;
+        mBoundPackageName = component.getPackageName();
+        mBoundVersion = version;
+        mBoundUserId = userId;
+        if (D) Log.d(mTag, "binding " + component + " (v" + version + ") (u" + userId + ")");
+        mContext.bindServiceAsUser(intent, this,
+                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE,
+                new UserHandle(userId));
     }
 
     public static boolean isSignatureMatch(Signature[] signatures,
@@ -275,106 +313,92 @@
         @Override
         public void onPackageUpdateFinished(String packageName, int uid) {
             synchronized (mLock) {
-                if (packageName.equals(mPackageName)) {
-                    // package updated, make sure to rebind
-                    unbindLocked();
-                }
-                // Need to check all packages because this method is also called when a
-                // system app is uninstalled and the stock version in reinstalled.
-                bindBestPackageLocked(null);
+                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
+                bindBestPackageLocked(null, forceRebind);
             }
         }
 
         @Override
         public void onPackageAdded(String packageName, int uid) {
             synchronized (mLock) {
-                if (packageName.equals(mPackageName)) {
-                    // package updated, make sure to rebind
-                    unbindLocked();
-                }
-                // check the new package is case it is better
-                bindBestPackageLocked(null);
+                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
+                bindBestPackageLocked(null, forceRebind);
             }
         }
 
         @Override
         public void onPackageRemoved(String packageName, int uid) {
             synchronized (mLock) {
-                if (packageName.equals(mPackageName)) {
-                    unbindLocked();
-                    // the currently bound package was removed,
-                    // need to search for a new package
-                    bindBestPackageLocked(null);
-                }
+                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
+                bindBestPackageLocked(null, forceRebind);
             }
         }
 
         @Override
         public boolean onPackageChanged(String packageName, int uid, String[] components) {
             synchronized (mLock) {
-                if (packageName.equals(mPackageName)) {
-                    // service enabled or disabled, make sure to rebind
-                    unbindLocked();
-                }
-                // the service might be disabled, need to search for a new
-                // package
-                bindBestPackageLocked(null);
+                final boolean forceRebind = Objects.equals(packageName, mBoundPackageName);
+                bindBestPackageLocked(null, forceRebind);
             }
             return super.onPackageChanged(packageName, uid, components);
         }
     };
 
     @Override
-    public void onServiceConnected(ComponentName name, IBinder binder) {
+    public void onServiceConnected(ComponentName component, IBinder binder) {
         synchronized (mLock) {
-            String packageName = name.getPackageName();
-            if (packageName.equals(mPackageName)) {
-                if (D) Log.d(mTag, packageName + " connected");
-                mBinder = binder;
+            if (component.equals(mBoundComponent)) {
+                if (D) Log.d(mTag, component + " connected");
+                mBoundService = binder;
                 if (mHandler !=null && mNewServiceWork != null) {
                     mHandler.post(mNewServiceWork);
                 }
             } else {
-                Log.w(mTag, "unexpected onServiceConnected: " + packageName);
+                Log.w(mTag, "unexpected onServiceConnected: " + component);
             }
         }
     }
 
     @Override
-    public void onServiceDisconnected(ComponentName name) {
+    public void onServiceDisconnected(ComponentName component) {
         synchronized (mLock) {
-            String packageName = name.getPackageName();
-            if (D) Log.d(mTag, packageName + " disconnected");
+            if (D) Log.d(mTag, component + " disconnected");
 
-            if (packageName.equals(mPackageName)) {
-                mBinder = null;
+            if (component.equals(mBoundComponent)) {
+                mBoundService = null;
             }
         }
     }
 
-    public String getBestPackageName() {
+    public @Nullable String getBestPackageName() {
         synchronized (mLock) {
-            return mPackageName;
+            return mBoundPackageName;
         }
     }
 
     public int getBestVersion() {
         synchronized (mLock) {
-            return mVersion;
+            return mBoundVersion;
         }
     }
 
-    public IBinder getBinder() {
+    public @Nullable IBinder getBinder() {
         synchronized (mLock) {
-            return mBinder;
+            return mBoundService;
         }
     }
 
-    public void switchUser() {
+    public void switchUser(int userId) {
         synchronized (mLock) {
-            if (!mIsMultiuser) {
-                unbindLocked();
-                bindBestPackageLocked(mServicePackageName);
+            mCurrentUserId = userId;
+            bindBestPackageLocked(mServicePackageName, false);
+        }
+    }
+
+    public void unlockUser(int userId) {
+        synchronized (mLock) {
+            if (userId == mCurrentUserId) {
+                bindBestPackageLocked(mServicePackageName, false);
             }
         }
     }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9dae740..104217a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1451,6 +1451,7 @@
     static final int VR_MODE_CHANGE_MSG = 63;
     static final int NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG = 64;
     static final int NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG = 65;
+    static final int NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG = 66;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1979,6 +1980,20 @@
                 }
                 break;
             }
+            case NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG: {
+                synchronized (ActivityManagerService.this) {
+                    for (int i = mTaskStackListeners.beginBroadcast() - 1; i >= 0; i--) {
+                        try {
+                            // Make a one-way callback to the listener
+                            mTaskStackListeners.getBroadcastItem(i).onPinnedStackAnimationEnded();
+                        } catch (RemoteException e){
+                            // Handled by the RemoteCallbackList
+                        }
+                    }
+                    mTaskStackListeners.finishBroadcast();
+                }
+                break;
+            }
             case NOTIFY_CLEARTEXT_NETWORK_MSG: {
                 final int uid = msg.arg1;
                 final byte[] firstPacket = (byte[]) msg.obj;
@@ -7161,8 +7176,8 @@
                 final Rect bounds = (mStackSupervisor.getStack(PINNED_STACK_ID) == null)
                         ? mDefaultPinnedStackBounds : null;
 
-                mStackSupervisor.moveActivityToStackLocked(
-                        r, PINNED_STACK_ID, "enterPictureInPicture", bounds);
+                mStackSupervisor.moveActivityToPinnedStackLocked(
+                        r, "enterPictureInPicture", bounds);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -10722,6 +10737,12 @@
             return;
         }
 
+        // We're only interested in providers that are encryption unaware, and
+        // we don't care about uninstalled apps, since there's no way they're
+        // running at this point.
+        final int matchFlags = GET_PROVIDERS | MATCH_ENCRYPTION_UNAWARE
+                | MATCH_DEBUG_TRIAGED_MISSING;
+
         synchronized (this) {
             final int NP = mProcessNames.getMap().size();
             for (int ip = 0; ip < NP; ip++) {
@@ -10736,8 +10757,7 @@
                         try {
                             final String pkgName = app.pkgList.keyAt(ig);
                             final PackageInfo pkgInfo = AppGlobals.getPackageManager()
-                                    .getPackageInfo(pkgName,
-                                            GET_PROVIDERS | MATCH_ENCRYPTION_UNAWARE, userId);
+                                    .getPackageInfo(pkgName, matchFlags, userId);
                             if (pkgInfo != null && !ArrayUtils.isEmpty(pkgInfo.providers)) {
                                 for (ProviderInfo provInfo : pkgInfo.providers) {
                                     Log.v(TAG, "Installing " + provInfo);
@@ -11052,6 +11072,16 @@
         mHandler.obtainMessage(NOTIFY_PINNED_ACTIVITY_RESTART_ATTEMPT_LISTENERS_MSG).sendToTarget();
     }
 
+    /** Notifies all listeners when the pinned stack animation ends. */
+    @Override
+    public void notifyPinnedStackAnimationEnded() {
+        synchronized (this) {
+            mHandler.removeMessages(NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG);
+            mHandler.obtainMessage(
+                    NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG).sendToTarget();
+        }
+    }
+
     @Override
     public void notifyCleartextNetwork(int uid, byte[] firstPacket) {
         mHandler.obtainMessage(NOTIFY_CLEARTEXT_NETWORK_MSG, uid, 0, firstPacket).sendToTarget();
@@ -14894,7 +14924,7 @@
                 pw.println(mi.hasActivities ? ",a" : ",e");
             } else {
                 pw.print(tag); pw.print(","); pw.print(mi.shortLabel); pw.print(",");
-                pw.println(mi.pss); pw.print(dumpSwapPss ? mi.swapPss : "N/A");
+                pw.print(mi.pss); pw.print(","); pw.println(dumpSwapPss ? mi.swapPss : "N/A");
             }
             if (mi.subitems != null) {
                 dumpMemItems(pw, prefix + "    ", mi.shortLabel, mi.subitems,
@@ -14959,6 +14989,9 @@
 
     private final void dumpApplicationMemoryUsageHeader(PrintWriter pw, long uptime,
             long realtime, boolean isCheckinRequest, boolean isCompact) {
+        if (isCompact) {
+            pw.print("version,"); pw.println(MEMINFO_COMPACT_VERSION);
+        }
         if (isCheckinRequest || isCompact) {
             // short checkin version
             pw.print("time,"); pw.print(uptime); pw.print(","); pw.println(realtime);
@@ -15017,6 +15050,9 @@
         return stringifySize(size * 1024, 1024);
     }
 
+    // Update this version number in case you change the 'compact' format
+    private static final int MEMINFO_COMPACT_VERSION = 1;
+
     final void dumpApplicationMemoryUsage(FileDescriptor fd,
             PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) {
         boolean dumpDetails = false;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 0bccffa..8560a9e 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1587,6 +1587,33 @@
                 // determined individually unlike other stacks where the visibility or fullscreen
                 // status of an activity in a previous task affects other.
                 behindFullscreenActivity = stackVisibility == STACK_INVISIBLE;
+            } else if (mStackId == HOME_STACK_ID) {
+                if (task.isHomeTask()) {
+                    if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home task: at " + task
+                            + " stackInvisible=" + stackInvisible
+                            + " behindFullscreenActivity=" + behindFullscreenActivity);
+                    // No other task in the home stack should be visible behind the home activity.
+                    // Home activities is usually a translucent activity with the wallpaper behind
+                    // them. However, when they don't have the wallpaper behind them, we want to
+                    // show activities in the next application stack behind them vs. another
+                    // task in the home stack like recents.
+                    behindFullscreenActivity = true;
+                } else if (task.isRecentsTask()
+                        && task.getTaskToReturnTo() == APPLICATION_ACTIVITY_TYPE) {
+                    if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+                            "Recents task returning to app: at " + task
+                                    + " stackInvisible=" + stackInvisible
+                                    + " behindFullscreenActivity=" + behindFullscreenActivity);
+                    // We don't want any other tasks in the home stack visible if the recents
+                    // activity is going to be returning to an application activity type.
+                    // We do this to preserve the visible order the user used to get into the
+                    // recents activity. The recents activity is normally translucent and if it
+                    // doesn't have the wallpaper behind it the next activity in the home stack
+                    // shouldn't be visible when the home stack is brought to the front to display
+                    // the recents activity from an app.
+                    behindFullscreenActivity = true;
+                }
+
             }
         }
 
@@ -1687,33 +1714,7 @@
                         + " behindFullscreenActivity=" + behindFullscreenActivity);
             // At this point, nothing else needs to be shown in this task.
             behindFullscreenActivity = true;
-        } else if (isHomeStack()) {
-            if (r.isHomeActivity()) {
-                if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home activity: at " + r
-                        + " stackInvisible=" + stackInvisible
-                        + " behindFullscreenActivity=" + behindFullscreenActivity);
-                // No other activity in the home stack should be visible behind the home activity.
-                // Home activities is usually a translucent activity with the wallpaper behind them.
-                // However, when they don't have the wallpaper behind them, we want to show
-                // activities in the next application stack behind them vs. another activity in the
-                // home stack like recents.
-                behindFullscreenActivity = true;
-            } else if (r.isRecentsActivity()
-                    && task.getTaskToReturnTo() == APPLICATION_ACTIVITY_TYPE) {
-                if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
-                        "Recents activity returning to app: at " + r
-                        + " stackInvisible=" + stackInvisible
-                        + " behindFullscreenActivity=" + behindFullscreenActivity);
-                // We don't want any other activities in the home stack visible if the recents
-                // activity is going to be returning to an application activity type.
-                // We do this to preserve the visible order the user used to get into the recents
-                // activity. The recents activity is normally translucent and if it doesn't have
-                // the wallpaper behind it the next activity in the home stack shouldn't be visible
-                // when the home stack is brought to the front to display the recents activity from
-                // an app.
-                behindFullscreenActivity = true;
-            }
-        } else if (r.frontOfTask && task.isOverHomeStack()) {
+        } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
             if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
                     + " stackInvisible=" + stackInvisible
                     + " behindFullscreenActivity=" + behindFullscreenActivity);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 0beef53..26108a3 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -148,6 +148,7 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.ActivityManagerService.ANIMATE;
 import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
 import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
 import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
@@ -2321,36 +2322,44 @@
             return false;
         }
 
-        moveActivityToStackLocked(r, PINNED_STACK_ID, "moveTopActivityToPinnedStack", null);
-        mWindowManager.animateResizePinnedStack(bounds);
+        moveActivityToPinnedStackLocked(r, "moveTopActivityToPinnedStack", bounds);
         return true;
     }
 
-    void moveActivityToStackLocked(ActivityRecord r, int stackId, String reason, Rect bounds) {
-        final TaskRecord task = r.task;
-        if (task.mActivities.size() == 1) {
-            // There is only one activity in the task. So, we can just move the task over to the
-            // stack without re-parenting the activity in a different task.
-            moveTaskToStackLocked(
-                    task.taskId, stackId, ON_TOP, FORCE_FOCUS, reason, true /* animate */);
-        } else {
-            final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
-            stack.moveActivityToStack(r);
+    void moveActivityToPinnedStackLocked(ActivityRecord r, String reason, Rect bounds) {
+        mWindowManager.deferSurfaceLayout();
+        try {
+            final TaskRecord task = r.task;
+
+            // Need to make sure the pinned stack exist so we can resize it below...
+            final ActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+
+            // Resize the pinned stack to match the current size of the task the activity we are
+            // going to be moving is currently contained in. We do this to have the right starting
+            // animation bounds for the pinned stack to the desired bounds the caller wants.
+            resizeStackLocked(PINNED_STACK_ID, task.mBounds, null /* tempTaskBounds */,
+                    null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
+                    true /* allowResizeInDockedMode */);
+
+            if (task.mActivities.size() == 1) {
+                // There is only one activity in the task. So, we can just move the task over to
+                // the stack without re-parenting the activity in a different task.
+                moveTaskToStackLocked(
+                        task.taskId, PINNED_STACK_ID, ON_TOP, FORCE_FOCUS, reason, !ANIMATE);
+            } else {
+                stack.moveActivityToStack(r);
+            }
+        } finally {
+            mWindowManager.continueSurfaceLayout();
         }
 
-        if (bounds != null) {
-            resizeStackLocked(stackId, bounds, null /* tempTaskBounds */,
-                    null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS, true);
-        }
-
-        // The task might have already been running and its visibility needs to be synchronized with
-        // the visibility of the stack / windows.
+        // The task might have already been running and its visibility needs to be synchronized
+        // with the visibility of the stack / windows.
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
 
-        if (stackId == PINNED_STACK_ID) {
-            mService.notifyActivityPinnedLocked();
-        }
+        mWindowManager.animateResizePinnedStack(bounds);
+        mService.notifyActivityPinnedLocked();
     }
 
     void positionTaskInStackLocked(int taskId, int stackId, int position) {
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index b360b89..28be456 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1001,7 +1001,7 @@
                 // If the activity is not focusable, we can't resume it, but still would like to
                 // make sure it becomes visible as it starts (this will also trigger entry
                 // animation). An example of this are PIP activities.
-                mTargetStack.ensureActivitiesVisibleLocked(mStartActivity, 0, !PRESERVE_WINDOWS);
+                mTargetStack.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
             }
         } else {
             mTargetStack.addRecentActivityLocked(mStartActivity);
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 10f0977..98a7ead 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -933,10 +933,15 @@
         }
         return false;
     }
+
     boolean isHomeTask() {
         return taskType == HOME_ACTIVITY_TYPE;
     }
 
+    boolean isRecentsTask() {
+        return taskType == RECENTS_ACTIVITY_TYPE;
+    }
+
     boolean isApplicationTask() {
         return taskType == APPLICATION_ACTIVITY_TYPE;
     }
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 4c269989..e5e86ac 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -1024,7 +1024,8 @@
             final IPackageManager pm = AppGlobals.getPackageManager();
             final ComponentName service = job.getService();
             try {
-                ServiceInfo si = pm.getServiceInfo(service, 0, UserHandle.getUserId(uid));
+                ServiceInfo si = pm.getServiceInfo(service,
+                        PackageManager.MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(uid));
                 if (si == null) {
                     throw new IllegalArgumentException("No such service " + service);
                 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index bf8e8fb..d2bcf13 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -39,6 +39,7 @@
 import static android.service.notification.NotificationListenerService.TRIM_FULL;
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
 import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_HIGH;
+import static android.service.notification.NotificationListenerService.Ranking.IMPORTANCE_NONE;
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -2001,6 +2002,9 @@
         @Override
         public void setImportanceFromAssistant(INotificationListener token, String key,
                 int importance, CharSequence explanation) throws RemoteException {
+            if (importance == IMPORTANCE_NONE) {
+                throw new IllegalArgumentException("blocking not allowed: key=" + key);
+            }
             final long identity = Binder.clearCallingIdentity();
             try {
                 synchronized (mNotificationList) {
@@ -3480,7 +3484,8 @@
         try {
             // TODO: it might be faster to return a boolean from package manager rather than the
             // whole application info. Revisit and make the API change.
-            ai = AppGlobals.getPackageManager().getApplicationInfo(pkg, 0, userId);
+            ai = AppGlobals.getPackageManager().getApplicationInfo(pkg,
+                    PackageManager.MATCH_DEBUG_TRIAGED_MISSING, userId);
             if (ai == null) {
                 Slog.w(TAG, "No application info for package " + pkg + " and user " + userId);
                 return false;
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index bba0d40..383c1ab 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -578,7 +578,8 @@
                     ZenRule rule = newConfig.automaticRules.get(newConfig.automaticRules.keyAt(i));
                     if (RULE_INSTANCE_GRACE_PERIOD < (currentTime - rule.creationTime)) {
                         try {
-                            mPm.getPackageInfo(rule.component.getPackageName(), 0);
+                            mPm.getPackageInfo(rule.component.getPackageName(),
+                                    PackageManager.MATCH_UNINSTALLED_PACKAGES);
                         } catch (PackageManager.NameNotFoundException e) {
                             newConfig.automaticRules.removeAt(i);
                         }
diff --git a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
index 6c338c1..9b5fde0 100644
--- a/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/DefaultPermissionGrantPolicy.java
@@ -262,6 +262,7 @@
                 grantRuntimePermissionsLPw(setupPackage, PHONE_PERMISSIONS, userId);
                 grantRuntimePermissionsLPw(setupPackage, CONTACTS_PERMISSIONS, userId);
                 grantRuntimePermissionsLPw(setupPackage, LOCATION_PERMISSIONS, userId);
+                grantRuntimePermissionsLPw(setupPackage, CAMERA_PERMISSIONS, userId);
             }
 
             // Camera
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c8645b4..04e4a8c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -8220,7 +8220,7 @@
      *
      * If {@code extractLibs} is true, native libraries are extracted from the app if required.
      */
-    public void derivePackageAbi(PackageParser.Package pkg, File scanFile,
+    private void derivePackageAbi(PackageParser.Package pkg, File scanFile,
                                  String cpuAbiOverride, boolean extractLibs)
             throws PackageManagerException {
         // TODO: We can probably be smarter about this stuff. For installed apps,
@@ -8301,16 +8301,17 @@
                 if (abi32 >= 0) {
                     final String abi = Build.SUPPORTED_32_BIT_ABIS[abi32];
                     if (abi64 >= 0) {
-                        pkg.applicationInfo.secondaryCpuAbi = abi;
+                        if (cpuAbiOverride == null && pkg.use32bitAbi) {
+                            pkg.applicationInfo.secondaryCpuAbi = pkg.applicationInfo.primaryCpuAbi;
+                            pkg.applicationInfo.primaryCpuAbi = abi;
+                        } else {
+                            pkg.applicationInfo.secondaryCpuAbi = abi;
+                        }
                     } else {
                         pkg.applicationInfo.primaryCpuAbi = abi;
                     }
                 }
-                if (cpuAbiOverride != null &&
-                        cpuAbiOverride.equals(pkg.applicationInfo.secondaryCpuAbi)) {
-                    pkg.applicationInfo.secondaryCpuAbi = pkg.applicationInfo.primaryCpuAbi;
-                    pkg.applicationInfo.primaryCpuAbi = cpuAbiOverride;
-                }
+
             } else {
                 String[] abiList = (cpuAbiOverride != null) ?
                         new String[] { cpuAbiOverride } : Build.SUPPORTED_ABIS;
@@ -14396,7 +14397,6 @@
         if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user);
 
         PackageSetting ps;
-        int removeUser = -1;
 
         synchronized (mPackages) {
             ps = mSettings.mPackages.get(packageName);
@@ -14411,7 +14411,9 @@
                     Slog.d(TAG, "Uninstalled child package:" + packageName + " for user:"
                             + ((user == null) ? UserHandle.USER_ALL : user));
                 }
-                if (!clearPackageStateForUser(ps, removeUser, outInfo)) {
+                final int removedUserId = (user != null) ? user.getIdentifier()
+                        : UserHandle.USER_ALL;
+                if (!clearPackageStateForUser(ps, removedUserId, outInfo)) {
                     return false;
                 }
                 markPackageUninstalledForUserLPw(ps, user);
@@ -14435,9 +14437,9 @@
                 if (ps.isAnyInstalled(sUserManager.getUserIds()) || keepUninstalledPackage) {
                     // Other user still have this package installed, so all
                     // we need to do is clear this user's data and save that
-                // it is uninstalled.
-                if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
-                    if (!clearPackageStateForUser(ps, removeUser, outInfo)) {
+                    // it is uninstalled.
+                    if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users");
+                    if (!clearPackageStateForUser(ps, user.getIdentifier(), outInfo)) {
                         return false;
                     }
                     scheduleWritePackageRestrictionsLocked(user);
@@ -14454,7 +14456,7 @@
                 // we need to do is clear this user's data and save that
                 // it is uninstalled.
                 if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app");
-                if (!clearPackageStateForUser(ps, removeUser, outInfo)) {
+                if (!clearPackageStateForUser(ps, user.getIdentifier(), outInfo)) {
                     return false;
                 }
                 scheduleWritePackageRestrictionsLocked(user);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 3e99a4c..0f51c82e 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -19,6 +19,7 @@
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
 
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -1582,7 +1583,7 @@
         }
 
         @Override
-        public void startRecording(IBinder sessionToken, int userId) {
+        public void startRecording(IBinder sessionToken, @Nullable Uri programHint, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
                     userId, "startRecording");
@@ -1590,7 +1591,8 @@
             try {
                 synchronized (mLock) {
                     try {
-                        getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording();
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId).startRecording(
+                                programHint);
                     } catch (RemoteException | SessionNotFoundException e) {
                         Slog.e(TAG, "error in startRecording", e);
                     }
@@ -2474,7 +2476,8 @@
         public void onSessionEvent(String eventType, Bundle eventArgs) {
             synchronized (mLock) {
                 if (DEBUG) {
-                    Slog.d(TAG, "onEvent(what=" + eventType + ", data=" + eventArgs + ")");
+                    Slog.d(TAG, "onEvent(eventType=" + eventType + ", eventArgs=" + eventArgs
+                            + ")");
                 }
                 if (mSessionState.session == null || mSessionState.client == null) {
                     return;
@@ -2491,7 +2494,7 @@
         public void onTimeShiftStatusChanged(int status) {
             synchronized (mLock) {
                 if (DEBUG) {
-                    Slog.d(TAG, "onTimeShiftStatusChanged()");
+                    Slog.d(TAG, "onTimeShiftStatusChanged(status=" + status + ")");
                 }
                 if (mSessionState.session == null || mSessionState.client == null) {
                     return;
@@ -2508,7 +2511,7 @@
         public void onTimeShiftStartPositionChanged(long timeMs) {
             synchronized (mLock) {
                 if (DEBUG) {
-                    Slog.d(TAG, "onTimeShiftStartPositionChanged()");
+                    Slog.d(TAG, "onTimeShiftStartPositionChanged(timeMs=" + timeMs + ")");
                 }
                 if (mSessionState.session == null || mSessionState.client == null) {
                     return;
@@ -2525,7 +2528,7 @@
         public void onTimeShiftCurrentPositionChanged(long timeMs) {
             synchronized (mLock) {
                 if (DEBUG) {
-                    Slog.d(TAG, "onTimeShiftCurrentPositionChanged()");
+                    Slog.d(TAG, "onTimeShiftCurrentPositionChanged(timeMs=" + timeMs + ")");
                 }
                 if (mSessionState.session == null || mSessionState.client == null) {
                     return;
@@ -2580,7 +2583,8 @@
         public void onRecordingStopped(Uri recordedProgramUri) {
             synchronized (mLock) {
                 if (DEBUG) {
-                    Slog.d(TAG, "onRecordingStopped()");
+                    Slog.d(TAG, "onRecordingStopped(recordedProgramUri=" + recordedProgramUri
+                            + ")");
                 }
                 if (mSessionState.session == null || mSessionState.client == null) {
                     return;
@@ -2598,7 +2602,7 @@
         public void onError(int error) {
             synchronized (mLock) {
                 if (DEBUG) {
-                    Slog.d(TAG, "onError()");
+                    Slog.d(TAG, "onError(error=" + error + ")");
                 }
                 if (mSessionState.session == null || mSessionState.client == null) {
                     return;
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index a8b72892..7244676 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -38,6 +38,7 @@
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.WindowManager.DOCKED_BOTTOM;
 import static android.view.WindowManager.DOCKED_INVALID;
@@ -945,6 +946,13 @@
             mDragResizing = false;
             mService.requestTraversal();
         }
+        if (mStackId == PINNED_STACK_ID) {
+            try {
+                mService.mActivityManager.notifyPinnedStackAnimationEnded();
+            } catch (RemoteException e) {
+                // I don't believe you...
+            }
+        }
     }
 
     @Override
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index c122c5a..f1cbb9a 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -774,9 +774,9 @@
     }
 
     /**
-     * Register a {@link PhoneAccount} for use by the system. When registering
-     * {@link PhoneAccount}s, existing registrations will be overwritten if the
-     * {@link PhoneAccountHandle} matches that of a {@link PhoneAccount} which is already
+     * Register a {@link PhoneAccount} for use by the system that will be stored in Device Encrypted
+     * storage. When registering {@link PhoneAccount}s, existing registrations will be overwritten
+     * if the {@link PhoneAccountHandle} matches that of a {@link PhoneAccount} which is already
      * registered. Once registered, the {@link PhoneAccount} is listed to the user as an option
      * when placing calls. The user may still need to enable the {@link PhoneAccount} within
      * the phone app settings before the account is usable.
@@ -1166,11 +1166,16 @@
     /**
      * Registers a new incoming call. A {@link ConnectionService} should invoke this method when it
      * has an incoming call. The specified {@link PhoneAccountHandle} must have been registered
-     * with {@link #registerPhoneAccount}. Once invoked, this method will cause the system to bind
-     * to the {@link ConnectionService} associated with the {@link PhoneAccountHandle} and request
-     * additional information about the call (See
-     * {@link ConnectionService#onCreateIncomingConnection}) before starting the incoming call UI.
-     *
+     * with {@link #registerPhoneAccount} and the user must have enabled the corresponding
+     * {@link PhoneAccount}. This can be checked using {@link #getPhoneAccount}. Once invoked, this
+     * method will cause the system to bind to the {@link ConnectionService} associated with the
+     * {@link PhoneAccountHandle} and request additional information about the call
+     * (See {@link ConnectionService#onCreateIncomingConnection}) before starting the incoming
+     * call UI.
+     * <p>
+     * A {@link SecurityException} will be thrown if either the {@link PhoneAccountHandle} does not
+     * correspond to a registered {@link PhoneAccount} or the associated {@link PhoneAccount} is not
+     * currently enabled by the user.
      * @param phoneAccount A {@link PhoneAccountHandle} registered with
      *            {@link #registerPhoneAccount}.
      * @param extras A bundle that will be passed through to
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index f74b93a..88b6270 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -43,6 +43,9 @@
 	link/TableMerger.cpp \
 	link/XmlReferenceLinker.cpp \
 	process/SymbolTable.cpp \
+	proto/ProtoHelpers.cpp \
+	proto/TableProtoDeserializer.cpp \
+	proto/TableProtoSerializer.cpp \
 	unflatten/BinaryResourceParser.cpp \
 	unflatten/ResChunkPullParser.cpp \
 	util/BigBuffer.cpp \
@@ -67,13 +70,14 @@
 	xml/XmlPullParser.cpp \
 	xml/XmlUtil.cpp
 
+sources += Format.proto
+
 testSources := \
 	compile/IdAssigner_test.cpp \
 	compile/PseudolocaleGenerator_test.cpp \
 	compile/Pseudolocalizer_test.cpp \
 	compile/XmlIdCollector_test.cpp \
 	filter/ConfigFilter_test.cpp \
-	flatten/FileExportWriter_test.cpp \
 	flatten/TableFlattener_test.cpp \
 	flatten/XmlFlattener_test.cpp \
 	link/AutoVersioner_test.cpp \
@@ -83,7 +87,7 @@
 	link/TableMerger_test.cpp \
 	link/XmlReferenceLinker_test.cpp \
 	process/SymbolTable_test.cpp \
-	unflatten/FileExportHeaderReader_test.cpp \
+	proto/TableProtoSerializer_test.cpp \
 	util/BigBuffer_test.cpp \
 	util/Maybe_test.cpp \
 	util/StringPiece_test.cpp \
@@ -105,6 +109,7 @@
 
 toolSources := \
 	compile/Compile.cpp \
+	dump/Dump.cpp \
 	link/Link.cpp
 
 hostLdLibs :=
@@ -119,6 +124,9 @@
 	libpng \
 	libbase
 
+hostSharedLibs := \
+	libprotobuf-cpp-lite
+
 ifneq ($(strip $(USE_MINGW)),)
 	hostStaticLibs += libz
 else
@@ -127,21 +135,23 @@
 
 cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG
 cppFlags := -std=c++11 -Wno-missing-field-initializers -fno-exceptions -fno-rtti
+protoIncludes := $(call generated-sources-dir-for,STATIC_LIBRARIES,libaapt2,HOST)
 
 # ==========================================================
 # Build the host static library: libaapt2
 # ==========================================================
 include $(CLEAR_VARS)
+LOCAL_MODULE_CLASS := STATIC_LIBRARIES
 LOCAL_MODULE := libaapt2
 
 LOCAL_SRC_FILES := $(sources)
 LOCAL_STATIC_LIBRARIES += $(hostStaticLibs)
 LOCAL_CFLAGS += $(cFlags)
 LOCAL_CPPFLAGS += $(cppFlags)
+LOCAL_C_INCLUDES += $(protoIncludes)
 
 include $(BUILD_HOST_STATIC_LIBRARY)
 
-
 # ==========================================================
 # Build the host tests: libaapt2_tests
 # ==========================================================
@@ -152,9 +162,11 @@
 LOCAL_SRC_FILES := $(testSources)
 
 LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
+LOCAL_SHARED_LIBRARIES += $(hostSharedLibs)
 LOCAL_LDLIBS += $(hostLdLibs)
 LOCAL_CFLAGS += $(cFlags)
 LOCAL_CPPFLAGS += $(cppFlags)
+LOCAL_C_INCLUDES += $(protoIncludes)
 
 include $(BUILD_HOST_NATIVE_TEST)
 
@@ -167,9 +179,11 @@
 LOCAL_SRC_FILES := $(main) $(toolSources)
 
 LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs)
+LOCAL_SHARED_LIBRARIES += $(hostSharedLibs)
 LOCAL_LDLIBS += $(hostLdLibs)
 LOCAL_CFLAGS += $(cFlags)
 LOCAL_CPPFLAGS += $(cppFlags)
+LOCAL_C_INCLUDES += $(protoIncludes)
 
 include $(BUILD_HOST_EXECUTABLE)
 
diff --git a/tools/aapt2/Format.proto b/tools/aapt2/Format.proto
new file mode 100644
index 0000000..d05425c
--- /dev/null
+++ b/tools/aapt2/Format.proto
@@ -0,0 +1,210 @@
+/*
+ * 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 optimize_for = LITE_RUNTIME;
+
+package aapt.pb;
+
+message ConfigDescription {
+	optional bytes data = 1;
+	optional string product = 2;
+}
+
+message StringPool {
+	optional bytes data = 1;
+}
+
+message CompiledFile {
+	message Symbol {
+		optional string resource_name = 1;
+		optional uint32 line_no = 2;
+	}
+	
+	optional string resource_name = 1;
+	optional ConfigDescription config = 2;
+	optional string source_path = 3;
+	repeated Symbol exported_symbols = 4;
+}
+
+message ResourceTable {
+	optional StringPool string_pool = 1;
+	optional StringPool source_pool = 2;
+	optional StringPool symbol_pool = 3;
+	repeated Package packages = 4;
+}
+
+message Package {
+	optional uint32 package_id = 1;
+	optional string package_name = 2;
+	repeated Type types = 3;
+}
+
+message Type {	
+	optional uint32 id = 1;
+	optional string name = 2;
+	repeated Entry entries = 3;
+}
+
+message SymbolStatus {
+	enum Visibility {
+		Unknown = 0;
+		Private = 1;
+		Public = 2;
+	}
+	optional Visibility visibility = 1;
+	optional Source source = 2;
+	optional string comment = 3;
+}
+
+message Entry {
+	optional uint32 id = 1;
+	optional string name = 2;
+	optional SymbolStatus symbol_status = 3;
+	repeated ConfigValue config_values = 4;
+}
+
+message ConfigValue {
+	optional ConfigDescription config = 1;
+	optional Value value = 2;
+}
+
+message Source {
+	optional uint32 path_idx = 1;
+	optional uint32 line_no = 2;
+	optional uint32 col_no = 3;
+}
+
+message Reference {
+	enum Type {
+		Ref = 0;
+		Attr = 1;
+	}
+	optional Type type = 1;
+	optional uint32 id = 2;
+	optional uint32 symbol_idx = 3;
+	optional bool private = 4;
+}
+
+message Id {
+}
+
+message String {
+	optional uint32 idx = 1;
+}
+
+message RawString {
+	optional uint32 idx = 1;
+}
+
+message FileReference {
+	optional uint32 path_idx = 1;
+}
+
+message Primitive {
+	optional uint32 type = 1;
+	optional uint32 data = 2;
+}
+
+message Attribute {
+	message Symbol {
+		optional Source source = 1;
+		optional string comment = 2;
+		optional Reference name = 3;
+		optional uint32 value = 4;
+	}
+	optional uint32 format_flags = 1;
+	optional int32 min_int = 2;
+	optional int32 max_int = 3;
+	repeated Symbol symbols = 4;
+}
+
+message Style {
+	message Entry {
+		optional Source source = 1;
+		optional string comment = 2;
+		optional Reference key = 3;
+		optional Item item = 4;
+	}
+
+	optional Reference parent = 1;
+	optional Source parent_source = 2;
+	repeated Entry entries = 3;
+}
+
+message Styleable {
+	message Entry {
+		optional Source source = 1;
+		optional string comment = 2;
+		optional Reference attr = 3;
+	}
+	repeated Entry entries = 1;
+}
+
+message Array {
+	message Entry {
+		optional Source source = 1;
+		optional string comment = 2;
+		optional Item item = 3;
+	}
+	repeated Entry entries = 1;
+}
+
+message Plural {
+	enum Arity {
+		Zero = 0;
+		One = 1;
+		Two = 2;
+		Few = 3;
+		Many = 4;
+		Other = 5;
+	}
+		
+	message Entry {
+		optional Source source = 1;
+		optional string comment = 2;
+		optional Arity arity = 3;
+		optional Item item = 4;
+	}
+	repeated Entry entries = 1;
+}
+
+message Item {
+	optional Reference ref = 1;
+	optional String str = 2;
+	optional RawString raw_str = 3;
+	optional FileReference file = 4;
+	optional Id id = 5;
+	optional Primitive prim = 6;
+}
+
+message CompoundValue {
+	optional Attribute attr = 1;
+	optional Style style = 2;
+	optional Styleable styleable = 3;
+	optional Array array = 4;
+	optional Plural plural = 5;
+}
+
+message Value {
+	optional Source source = 1;
+	optional string comment = 2;
+	optional bool weak = 3;
+	
+	optional Item item = 4;
+	optional CompoundValue compound_value = 5;	
+}
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 248e7ad..a2fadd9 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -23,6 +23,7 @@
 
 extern int compile(const std::vector<StringPiece>& args);
 extern int link(const std::vector<StringPiece>& args);
+extern int dump(const std::vector<StringPiece>& args);
 
 } // namespace aapt
 
@@ -41,12 +42,14 @@
             return aapt::compile(args);
         } else if (command == "link" || command == "l") {
             return aapt::link(args);
+        } else if (command == "dump" || command == "d") {
+            return aapt::dump(args);
         }
         std::cerr << "unknown command '" << command << "'\n";
     } else {
         std::cerr << "no command specified\n";
     }
 
-    std::cerr << "\nusage: aapt2 [compile|link] ..." << std::endl;
+    std::cerr << "\nusage: aapt2 [compile|link|dump] ..." << std::endl;
     return 1;
 }
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 07f62af..74c48b0 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -51,6 +51,10 @@
 }
 
 bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
+    if (str.empty()) {
+        return false;
+    }
+
     size_t offset = 0;
     bool priv = false;
     if (str.data()[0] == u'*') {
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 64ca971..a0fbcc6 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -45,7 +45,8 @@
  * `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix
  * was present.
  */
-bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource, bool* outPrivate);
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource,
+                       bool* outPrivate = nullptr);
 
 /*
  * Returns true if the string was parsed as a reference (@[+][package:]type/name), with
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index c9f93e1..7425f97 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -58,6 +58,8 @@
     EXPECT_TRUE(ResourceUtils::parseResourceName(u"*android:color/foo", &actual, &actualPriv));
     EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
     EXPECT_TRUE(actualPriv);
+
+    EXPECT_FALSE(ResourceUtils::parseResourceName(StringPiece16(), &actual, &actualPriv));
 }
 
 TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index b93e6d8..ab9c792 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -19,7 +19,6 @@
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
 #include "util/Util.h"
-#include "flatten/ResourceTypeExtensions.h"
 
 #include <androidfw/ResourceTypes.h>
 #include <limits>
@@ -47,7 +46,7 @@
 }
 
 bool RawString::flatten(android::Res_value* outValue) const {
-    outValue->dataType = ExtendedTypes::TYPE_RAW_STRING;
+    outValue->dataType = android::Res_value::TYPE_STRING;
     outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex()));
     return true;
 }
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 8e317db..dc2e28e 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -154,8 +154,8 @@
     bool privateReference = false;
 
     Reference();
-    Reference(const ResourceNameRef& n, Type type = Type::kResource);
-    Reference(const ResourceId& i, Type type = Type::kResource);
+    explicit Reference(const ResourceNameRef& n, Type type = Type::kResource);
+    explicit Reference(const ResourceId& i, Type type = Type::kResource);
 
     bool flatten(android::Res_value* outValue) const override;
     Reference* clone(StringPool* newPool) const override;
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index 94042e3..5493039 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -18,6 +18,7 @@
 #define AAPT_VALUE_VISITOR_H
 
 #include "ResourceValues.h"
+#include "ResourceTable.h"
 
 namespace aapt {
 
@@ -140,6 +141,23 @@
     return visitor.value;
 }
 
+
+inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* visitor) {
+    for (auto& type : pkg->types) {
+        for (auto& entry : type->entries) {
+            for (auto& configValue : entry->values) {
+                configValue.value->accept(visitor);
+            }
+        }
+    }
+}
+
+inline void visitAllValuesInTable(ResourceTable* table, RawValueVisitor* visitor) {
+    for (auto& pkg : table->packages) {
+        visitAllValuesInPackage(pkg.get(), visitor);
+    }
+}
+
 } // namespace aapt
 
 #endif // AAPT_VALUE_VISITOR_H
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 689ace6..1eefb82 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -24,15 +24,17 @@
 #include "compile/PseudolocaleGenerator.h"
 #include "compile/XmlIdCollector.h"
 #include "flatten/Archive.h"
-#include "flatten/FileExportWriter.h"
-#include "flatten/TableFlattener.h"
 #include "flatten/XmlFlattener.h"
+#include "proto/ProtoSerialize.h"
 #include "util/Files.h"
 #include "util/Maybe.h"
 #include "util/Util.h"
 #include "xml/XmlDom.h"
 #include "xml/XmlPullParser.h"
 
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <google/protobuf/io/coded_stream.h>
+
 #include <dirent.h>
 #include <fstream>
 #include <string>
@@ -232,34 +234,95 @@
         }
     }
 
-    // Assign IDs to prepare the table for flattening.
-    IdAssigner idAssigner;
-    if (!idAssigner.consume(context, &table)) {
-        return false;
-    }
-
-    // Flatten the table.
-    BigBuffer buffer(1024);
-    TableFlattenerOptions tableFlattenerOptions;
-    tableFlattenerOptions.useExtendedChunks = true;
-    TableFlattener flattener(&buffer, tableFlattenerOptions);
-    if (!flattener.consume(context, &table)) {
-        return false;
-    }
-
+    // Create the file/zip entry.
     if (!writer->startEntry(outputPath, 0)) {
         context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
         return false;
     }
 
-    if (writer->writeEntry(buffer)) {
-        if (writer->finishEntry()) {
-            return true;
+    std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table);
+
+    // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface.
+    {
+        google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+
+        if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
+            context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+            return false;
         }
     }
 
-    context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
-    return false;
+    if (!writer->finishEntry()) {
+        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to finish entry");
+        return false;
+    }
+    return true;
+}
+
+static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const ResourceFile& file,
+                                         const BigBuffer& buffer, IArchiveWriter* writer,
+                                         IDiagnostics* diag) {
+    // Start the entry so we can write the header.
+    if (!writer->startEntry(outputPath, 0)) {
+        diag->error(DiagMessage(outputPath) << "failed to open file");
+        return false;
+    }
+
+    // Create the header.
+    std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file);
+
+    {
+        // The stream must be destroyed before we finish the entry, or else
+        // some data won't be flushed.
+        // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
+        // interface.
+        google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+        CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get());
+        for (const BigBuffer::Block& block : buffer) {
+            if (!outputStream.Write(block.buffer.get(), block.size)) {
+                diag->error(DiagMessage(outputPath) << "failed to write data");
+                return false;
+            }
+        }
+    }
+
+    if (!writer->finishEntry()) {
+        diag->error(DiagMessage(outputPath) << "failed to finish writing data");
+        return false;
+    }
+    return true;
+}
+
+static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const ResourceFile& file,
+                                       const android::FileMap& map, IArchiveWriter* writer,
+                                       IDiagnostics* diag) {
+    // Start the entry so we can write the header.
+    if (!writer->startEntry(outputPath, 0)) {
+        diag->error(DiagMessage(outputPath) << "failed to open file");
+        return false;
+    }
+
+    // Create the header.
+    std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(file);
+
+    {
+        // The stream must be destroyed before we finish the entry, or else
+        // some data won't be flushed.
+        // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
+        // interface.
+        google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
+        CompiledFileOutputStream outputStream(&adaptor, pbCompiledFile.get());
+        if (!outputStream.Write(map.getDataPtr(), map.getDataLength())) {
+            diag->error(DiagMessage(outputPath) << "failed to write data");
+            return false;
+        }
+    }
+
+    if (!writer->finishEntry()) {
+        diag->error(DiagMessage(outputPath) << "failed to finish writing data");
+        return false;
+    }
+    return true;
 }
 
 static bool compileXml(IAaptContext* context, const CompileOptions& options,
@@ -267,7 +330,6 @@
                        const std::string& outputPath) {
 
     std::unique_ptr<xml::XmlResource> xmlRes;
-
     {
         std::ifstream fin(pathData.source.path, std::ifstream::binary);
         if (!fin) {
@@ -295,30 +357,18 @@
     xmlRes->file.source = pathData.source;
 
     BigBuffer buffer(1024);
-    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &xmlRes->file);
-
     XmlFlattenerOptions xmlFlattenerOptions;
     xmlFlattenerOptions.keepRawValues = true;
-    XmlFlattener flattener(fileExportWriter.getBuffer(), xmlFlattenerOptions);
+    XmlFlattener flattener(&buffer, xmlFlattenerOptions);
     if (!flattener.consume(context, xmlRes.get())) {
         return false;
     }
 
-    fileExportWriter.finish();
-
-    if (!writer->startEntry(outputPath, 0)) {
-        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
+    if (!writeHeaderAndBufferToWriter(outputPath, xmlRes->file, buffer, writer,
+                                      context->getDiagnostics())) {
         return false;
     }
-
-    if (writer->writeEntry(buffer)) {
-        if (writer->finishEntry()) {
-            return true;
-        }
-    }
-
-    context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
-    return false;
+    return true;
 }
 
 static bool compilePng(IAaptContext* context, const CompileOptions& options,
@@ -330,8 +380,6 @@
     resFile.config = pathData.config;
     resFile.source = pathData.source;
 
-    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
-
     {
         std::ifstream fin(pathData.source.path, std::ifstream::binary);
         if (!fin) {
@@ -340,26 +388,16 @@
         }
 
         Png png(context->getDiagnostics());
-        if (!png.process(pathData.source, &fin, fileExportWriter.getBuffer(), {})) {
+        if (!png.process(pathData.source, &fin, &buffer, {})) {
             return false;
         }
     }
 
-    fileExportWriter.finish();
-
-    if (!writer->startEntry(outputPath, 0)) {
-        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
+    if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer,
+                                      context->getDiagnostics())) {
         return false;
     }
-
-    if (writer->writeEntry(buffer)) {
-        if (writer->finishEntry()) {
-            return true;
-        }
-    }
-
-    context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
-    return false;
+    return true;
 }
 
 static bool compileFile(IAaptContext* context, const CompileOptions& options,
@@ -371,8 +409,6 @@
     resFile.config = pathData.config;
     resFile.source = pathData.source;
 
-    ChunkWriter fileExportWriter = wrapBufferWithFileExportHeader(&buffer, &resFile);
-
     std::string errorStr;
     Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr);
     if (!f) {
@@ -380,35 +416,10 @@
         return false;
     }
 
-    if (!writer->startEntry(outputPath, 0)) {
-        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
+    if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer,
+                                    context->getDiagnostics())) {
         return false;
     }
-
-    // Manually set the size and don't call finish(). This is because we are not copying from
-    // the buffer the entire file.
-    fileExportWriter.getChunkHeader()->size =
-            util::hostToDevice32(buffer.size() + f.value().getDataLength());
-
-    if (!writer->writeEntry(buffer)) {
-        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
-        return false;
-    }
-
-    // Only write if we have something to write. This is because mmap fails with length of 0,
-    // but we still want to compile the file to get the resource ID.
-    if (f.value().getDataPtr() && f.value().getDataLength() > 0) {
-        if (!writer->writeEntry(f.value().getDataPtr(), f.value().getDataLength())) {
-            context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
-            return false;
-        }
-    }
-
-    if (!writer->finishEntry()) {
-        context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
-        return false;
-    }
-
     return true;
 }
 
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index 80c6bbc..aa4a580 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -64,14 +64,12 @@
                     // Mark entry ID as taken.
                     if (!usedEntryIds.insert(entry->id.value()).second) {
                         // This ID existed before!
-                        ResourceNameRef nameRef =
-                                { package->name, type->type, entry->name };
-                        ResourceId takenId(package->id.value(), type->id.value(),
-                                           entry->id.value());
+                        ResourceNameRef nameRef(package->name, type->type, entry->name);
                         context->getDiagnostics()->error(DiagMessage()
                                                          << "resource '" << nameRef << "' "
-                                                         << "has duplicate ID '"
-                                                         << takenId << "'");
+                                                         << "has duplicate entry ID "
+                                                         << std::hex << (int) entry->id.value()
+                                                         << std::dec);
                         return false;
                     }
                 }
diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp
new file mode 100644
index 0000000..915fae8
--- /dev/null
+++ b/tools/aapt2/dump/Dump.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 "Debug.h"
+#include "Diagnostics.h"
+#include "Flags.h"
+#include "process/IResourceTableConsumer.h"
+#include "proto/ProtoSerialize.h"
+#include "util/Files.h"
+#include "util/StringPiece.h"
+
+#include <vector>
+
+namespace aapt {
+
+//struct DumpOptions {
+//
+//};
+
+void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data, size_t len,
+                      const Source& source, IAaptContext* context) {
+    std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(pbFile, source,
+                                                                       context->getDiagnostics());
+    if (!file) {
+        return;
+    }
+
+    std::cout << "Resource: " << file->name << "\n"
+              << "Config:   " << file->config << "\n"
+              << "Source:   " << file->source << "\n";
+}
+
+void dumpCompiledTable(const pb::ResourceTable& pbTable, const Source& source,
+                       IAaptContext* context) {
+    std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source,
+                                                                  context->getDiagnostics());
+    if (!table) {
+        return;
+    }
+
+    Debug::printTable(table.get());
+}
+
+void tryDumpFile(IAaptContext* context, const std::string& filePath) {
+    std::string err;
+    Maybe<android::FileMap> file = file::mmapPath(filePath, &err);
+    if (!file) {
+        context->getDiagnostics()->error(DiagMessage(filePath) << err);
+        return;
+    }
+
+    android::FileMap* fileMap = &file.value();
+
+    // Try as a compiled table.
+    pb::ResourceTable pbTable;
+    if (pbTable.ParseFromArray(fileMap->getDataPtr(), fileMap->getDataLength())) {
+        dumpCompiledTable(pbTable, Source(filePath), context);
+        return;
+    }
+
+    // Try as a compiled file.
+    CompiledFileInputStream input(fileMap->getDataPtr(), fileMap->getDataLength());
+    if (const pb::CompiledFile* pbFile = input.CompiledFile()) {
+       dumpCompiledFile(*pbFile, input.data(), input.size(), Source(filePath), context);
+       return;
+    }
+}
+
+class DumpContext : public IAaptContext {
+public:
+    IDiagnostics* getDiagnostics() override {
+        return &mDiagnostics;
+    }
+
+    NameMangler* getNameMangler() override {
+        abort();
+        return nullptr;
+    }
+
+    StringPiece16 getCompilationPackage() override {
+        return {};
+    }
+
+    uint8_t getPackageId() override {
+        return 0;
+    }
+
+    ISymbolTable* getExternalSymbols() override {
+        abort();
+        return nullptr;
+    }
+
+private:
+    StdErrDiagnostics mDiagnostics;
+};
+
+/**
+ * Entry point for dump command.
+ */
+int dump(const std::vector<StringPiece>& args) {
+    //DumpOptions options;
+    Flags flags = Flags();
+    if (!flags.parse("aapt2 dump", args, &std::cerr)) {
+        return 1;
+    }
+
+    DumpContext context;
+
+    for (const std::string& arg : flags.getArgs()) {
+        tryDumpFile(&context, arg);
+    }
+    return 0;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index 6da1d2a..34c10ad 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -22,6 +22,7 @@
 #include "util/Files.h"
 #include "util/StringPiece.h"
 
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
 #include <fstream>
 #include <memory>
 #include <string>
@@ -40,13 +41,18 @@
     size_t uncompressedSize;
 };
 
-struct IArchiveWriter {
+struct IArchiveWriter : public google::protobuf::io::CopyingOutputStream {
     virtual ~IArchiveWriter() = default;
 
     virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
     virtual bool writeEntry(const BigBuffer& buffer) = 0;
     virtual bool writeEntry(const void* data, size_t len) = 0;
     virtual bool finishEntry() = 0;
+
+    // CopyingOutputStream implementations.
+    bool Write(const void* buffer, int size) override {
+        return writeEntry(buffer, size);
+    }
 };
 
 std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
diff --git a/tools/aapt2/flatten/FileExportWriter.h b/tools/aapt2/flatten/FileExportWriter.h
deleted file mode 100644
index 7688fa7..0000000
--- a/tools/aapt2/flatten/FileExportWriter.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_FLATTEN_FILEEXPORTWRITER_H
-#define AAPT_FLATTEN_FILEEXPORTWRITER_H
-
-#include "StringPool.h"
-
-#include "flatten/ResourceTypeExtensions.h"
-#include "flatten/ChunkWriter.h"
-#include "process/IResourceTableConsumer.h"
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <utils/misc.h>
-
-namespace aapt {
-
-static ChunkWriter wrapBufferWithFileExportHeader(BigBuffer* buffer, ResourceFile* res) {
-    ChunkWriter fileExportWriter(buffer);
-    FileExport_header* fileExport = fileExportWriter.startChunk<FileExport_header>(
-            RES_FILE_EXPORT_TYPE);
-
-    ExportedSymbol* symbolRefs = nullptr;
-    if (!res->exportedSymbols.empty()) {
-        symbolRefs = fileExportWriter.nextBlock<ExportedSymbol>(
-                res->exportedSymbols.size());
-    }
-    fileExport->exportedSymbolCount = util::hostToDevice32(res->exportedSymbols.size());
-
-    StringPool symbolExportPool;
-    memcpy(fileExport->magic, "AAPT", NELEM(fileExport->magic));
-    fileExport->config = res->config;
-    fileExport->config.swapHtoD();
-    fileExport->name.index = util::hostToDevice32(symbolExportPool.makeRef(res->name.toString())
-                                                  .getIndex());
-    fileExport->source.index = util::hostToDevice32(symbolExportPool.makeRef(util::utf8ToUtf16(
-            res->source.path)).getIndex());
-
-    for (const SourcedResourceName& name : res->exportedSymbols) {
-        symbolRefs->name.index = util::hostToDevice32(symbolExportPool.makeRef(name.name.toString())
-                                                      .getIndex());
-        symbolRefs->line = util::hostToDevice32(name.line);
-        symbolRefs++;
-    }
-
-    StringPool::flattenUtf16(fileExportWriter.getBuffer(), symbolExportPool);
-    return fileExportWriter;
-}
-
-} // namespace aapt
-
-#endif /* AAPT_FLATTEN_FILEEXPORTWRITER_H */
diff --git a/tools/aapt2/flatten/FileExportWriter_test.cpp b/tools/aapt2/flatten/FileExportWriter_test.cpp
deleted file mode 100644
index 32fc203..0000000
--- a/tools/aapt2/flatten/FileExportWriter_test.cpp
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Resource.h"
-
-#include "flatten/FileExportWriter.h"
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-
-#include "test/Common.h"
-
-#include <gtest/gtest.h>
-
-namespace aapt {
-
-TEST(FileExportWriterTest, FlattenResourceFileDataWithNoExports) {
-    ResourceFile resFile = {
-            test::parseNameOrDie(u"@android:layout/main.xml"),
-            test::parseConfigOrDie("sw600dp-v4"),
-            Source{ "res/layout/main.xml" },
-    };
-
-    BigBuffer buffer(1024);
-    ChunkWriter writer = wrapBufferWithFileExportHeader(&buffer, &resFile);
-    *writer.getBuffer()->nextBlock<uint32_t>() = 42u;
-    writer.finish();
-
-    std::unique_ptr<uint8_t[]> data = util::copy(buffer);
-
-    // There should be more data (string pool) besides the header and our data.
-    ASSERT_GT(buffer.size(), sizeof(FileExport_header) + sizeof(uint32_t));
-
-    // Write at the end of this chunk is our data.
-    uint32_t* val = (uint32_t*)(data.get() + buffer.size()) - 1;
-    EXPECT_EQ(*val, 42u);
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index 02bff2c..3e20ad6 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -22,208 +22,6 @@
 namespace aapt {
 
 /**
- * New android::ResChunk_header types defined
- * for AAPT to use.
- *
- * TODO(adamlesinski): Consider reserving these
- * enums in androidfw/ResourceTypes.h to avoid
- * future collisions.
- */
-enum {
-    /**
-     * A chunk that contains an entire file that
-     * has been compiled.
-     */
-    RES_FILE_EXPORT_TYPE = 0x000c,
-
-    RES_TABLE_PUBLIC_TYPE = 0x000d,
-
-    /**
-     * A chunk that holds the string pool
-     * for source entries (path/to/source:line).
-     */
-    RES_TABLE_SOURCE_POOL_TYPE = 0x000e,
-
-    /**
-     * A chunk holding names of externally
-     * defined symbols and offsets to where
-     * they are referenced in the table.
-     */
-    RES_TABLE_SYMBOL_TABLE_TYPE = 0x000f,
-};
-
-/**
- * New resource types that are meant to only be used
- * by AAPT and will not end up on the device.
- */
-struct ExtendedTypes {
-    enum {
-        /**
-         * A raw string value that hasn't had its escape sequences
-         * processed nor whitespace removed.
-         */
-        TYPE_RAW_STRING = 0xfe,
-    };
-};
-
-/**
- * New types for a ResTable_map.
- */
-struct ExtendedResTableMapTypes {
-    enum {
-        /**
-         * Type that contains the source path of the next item in the map.
-         */
-        ATTR_SOURCE_PATH = Res_MAKEINTERNAL(0xffff),
-
-        /**
-         * Type that contains the source line of the next item in the map.
-         */
-        ATTR_SOURCE_LINE = Res_MAKEINTERNAL(0xfffe),
-
-        /**
-         * Type that contains the comment of the next item in the map.
-         */
-        ATTR_COMMENT = Res_MAKEINTERNAL(0xfffd)
-    };
-};
-
-/**
- * Followed by exportedSymbolCount ExportedSymbol structs, followed by the string pool.
- */
-struct FileExport_header {
-    android::ResChunk_header header;
-
-    /**
-     * MAGIC value. Must be 'AAPT' (0x41415054)
-     */
-    uint8_t magic[4];
-
-    /**
-     * Version of AAPT that built this file.
-     */
-    uint32_t version;
-
-    /**
-     * The resource name.
-     */
-    android::ResStringPool_ref name;
-
-    /**
-     * Configuration of this file.
-     */
-    android::ResTable_config config;
-
-    /**
-     * Original source path of this file.
-     */
-    android::ResStringPool_ref source;
-
-    /**
-     * Number of symbols exported by this file.
-     */
-    uint32_t exportedSymbolCount;
-};
-
-struct ExportedSymbol {
-    android::ResStringPool_ref name;
-    uint32_t line;
-};
-
-struct Public_header {
-    android::ResChunk_header header;
-
-    /**
-     * The ID of the type this structure refers to.
-     */
-    uint8_t typeId;
-
-    /**
-     * Reserved. Must be 0.
-     */
-    uint8_t res0;
-
-    /**
-     * Reserved. Must be 0.
-     */
-    uint16_t res1;
-
-    /**
-     * Number of public entries.
-     */
-    uint32_t count;
-};
-
-/**
- * A structure representing source data for a resource entry.
- * Appears after an android::ResTable_entry or android::ResTable_map_entry.
- *
- * TODO(adamlesinski): This causes some issues when runtime code checks
- * the size of an android::ResTable_entry. It assumes it is an
- * android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
- * which may not be true if this structure is present.
- */
-struct ResTable_entry_source {
-    /**
-     * File path reference.
-     */
-    android::ResStringPool_ref path;
-
-    /**
-     * Line number this resource was defined on.
-     */
-    uint32_t line;
-
-    /**
-     * Comment string reference.
-     */
-    android::ResStringPool_ref comment;
-};
-
-struct Public_entry {
-    uint16_t entryId;
-
-    enum : uint16_t {
-        kUndefined = 0,
-        kPublic = 1,
-        kPrivate = 2,
-    };
-
-    uint16_t state;
-    android::ResStringPool_ref key;
-    ResTable_entry_source source;
-};
-
-/**
- * A chunk with type RES_TABLE_SYMBOL_TABLE_TYPE.
- * Following the header are count number of SymbolTable_entry
- * structures, followed by an android::ResStringPool_header.
- */
-struct SymbolTable_header {
-    android::ResChunk_header header;
-
-    /**
-     * Number of SymbolTable_entry structures following
-     * this header.
-     */
-    uint32_t count;
-};
-
-struct SymbolTable_entry {
-    /**
-     * Offset from the beginning of the resource table
-     * where the symbol entry is referenced.
-     */
-    uint32_t offset;
-
-    /**
-     * The index into the string pool where the name of this
-     * symbol exists.
-     */
-    android::ResStringPool_ref name;
-};
-
-/**
  * An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout
  * struct.
  */
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 26d7c2c..71ab3db 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -51,157 +51,49 @@
     dst[i] = 0;
 }
 
+static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
+   if (a.key.id) {
+       if (b.key.id) {
+           return a.key.id.value() < b.key.id.value();
+       }
+       return true;
+   } else if (!b.key.id) {
+       return a.key.name.value() < b.key.name.value();
+   }
+   return false;
+}
+
 struct FlatEntry {
     ResourceEntry* entry;
     Value* value;
 
     // The entry string pool index to the entry's name.
     uint32_t entryKey;
-
-    // The source string pool index to the source file path.
-    uint32_t sourcePathKey;
-    uint32_t sourceLine;
-
-    // The source string pool index to the comment.
-    uint32_t commentKey;
 };
 
-class SymbolWriter {
+class MapFlattenVisitor : public RawValueVisitor {
 public:
-    struct Entry {
-        StringPool::Ref name;
-        size_t offset;
-    };
-
-    std::vector<Entry> symbols;
-
-    explicit SymbolWriter(StringPool* pool) : mPool(pool) {
-    }
-
-    void addSymbol(const Reference& ref, size_t offset) {
-        const ResourceName& name = ref.name.value();
-        std::u16string fullName;
-        if (ref.privateReference) {
-            fullName += u"*";
-        }
-
-        if (!name.package.empty()) {
-            fullName += name.package + u":";
-        }
-        fullName += toString(name.type).toString() + u"/" + name.entry;
-        symbols.push_back(Entry{ mPool->makeRef(fullName), offset });
-    }
-
-    void shiftAllOffsets(size_t offset) {
-        for (Entry& entry : symbols) {
-            entry.offset += offset;
-        }
-    }
-
-private:
-    StringPool* mPool;
-};
-
-struct MapFlattenVisitor : public RawValueVisitor {
     using RawValueVisitor::visit;
 
-    SymbolWriter* mSymbols;
-    FlatEntry* mEntry;
-    BigBuffer* mBuffer;
-    StringPool* mSourcePool;
-    StringPool* mCommentPool;
-    bool mUseExtendedChunks;
-
-    size_t mEntryCount = 0;
-    const Reference* mParent = nullptr;
-
-    MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer,
-                      StringPool* sourcePool, StringPool* commentPool,
-                      bool useExtendedChunks) :
-            mSymbols(symbols), mEntry(entry), mBuffer(buffer), mSourcePool(sourcePool),
-            mCommentPool(commentPool), mUseExtendedChunks(useExtendedChunks) {
-    }
-
-    void flattenKey(Reference* key, ResTable_map* outEntry) {
-        if (!key->id || (key->privateReference && mUseExtendedChunks)) {
-            assert(key->name && "reference must have a name");
-
-            outEntry->name.ident = util::hostToDevice32(0);
-            mSymbols->addSymbol(*key, (mBuffer->size() - sizeof(ResTable_map)) +
-                                    offsetof(ResTable_map, name));
-        } else {
-            outEntry->name.ident = util::hostToDevice32(key->id.value().id);
-        }
-    }
-
-    void flattenValue(Item* value, ResTable_map* outEntry) {
-        bool privateRef = false;
-        if (Reference* ref = valueCast<Reference>(value)) {
-            privateRef = ref->privateReference && mUseExtendedChunks;
-            if (!ref->id || privateRef) {
-                assert(ref->name && "reference must have a name");
-
-                mSymbols->addSymbol(*ref, (mBuffer->size() - sizeof(ResTable_map)) +
-                                        offsetof(ResTable_map, value) + offsetof(Res_value, data));
-            }
-        }
-
-        bool result = value->flatten(&outEntry->value);
-        if (privateRef) {
-            outEntry->value.data = 0;
-        }
-        assert(result && "flatten failed");
-    }
-
-    void flattenEntry(Reference* key, Item* value) {
-        ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
-        flattenKey(key, outEntry);
-        flattenValue(value, outEntry);
-        outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
-        mEntryCount++;
-    }
-
-    void flattenMetaData(Value* value) {
-        if (!mUseExtendedChunks) {
-            return;
-        }
-
-        Reference key(ResourceId{ ExtendedResTableMapTypes::ATTR_SOURCE_PATH });
-        StringPool::Ref sourcePathRef = mSourcePool->makeRef(
-                util::utf8ToUtf16(value->getSource().path));
-        BinaryPrimitive val(Res_value::TYPE_INT_DEC,
-                            static_cast<uint32_t>(sourcePathRef.getIndex()));
-        flattenEntry(&key, &val);
-
-        if (value->getSource().line) {
-            key.id = ResourceId(ExtendedResTableMapTypes::ATTR_SOURCE_LINE);
-            val.value.data = static_cast<uint32_t>(value->getSource().line.value());
-            flattenEntry(&key, &val);
-        }
-
-        if (!value->getComment().empty()) {
-            key.id = ResourceId(ExtendedResTableMapTypes::ATTR_COMMENT);
-            StringPool::Ref commentRef = mCommentPool->makeRef(value->getComment());
-            val.value.data = static_cast<uint32_t>(commentRef.getIndex());
-            flattenEntry(&key, &val);
-        }
+    MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer) :
+            mOutEntry(outEntry), mBuffer(buffer) {
     }
 
     void visit(Attribute* attr) override {
         {
-            Reference key(ResourceId{ ResTable_map::ATTR_TYPE });
+            Reference key = Reference(ResTable_map::ATTR_TYPE);
             BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask);
             flattenEntry(&key, &val);
         }
 
         if (attr->minInt != std::numeric_limits<int32_t>::min()) {
-            Reference key(ResourceId{ ResTable_map::ATTR_MIN });
+            Reference key = Reference(ResTable_map::ATTR_MIN);
             BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt));
             flattenEntry(&key, &val);
         }
 
         if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
-            Reference key(ResourceId{ ResTable_map::ATTR_MAX });
+            Reference key = Reference(ResTable_map::ATTR_MAX);
             BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt));
             flattenEntry(&key, &val);
         }
@@ -212,22 +104,11 @@
         }
     }
 
-    static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) {
-        if (a.key.id) {
-            if (b.key.id) {
-                return a.key.id.value() < b.key.id.value();
-            }
-            return true;
-        } else if (!b.key.id) {
-            return a.key.name.value() < b.key.name.value();
-        }
-        return false;
-    }
-
     void visit(Style* style) override {
         if (style->parent) {
-            // Parents are treated a bit differently, so record the existence and move on.
-            mParent = &style->parent.value();
+            const Reference& parentRef = style->parent.value();
+            assert(parentRef.id && "parent has no ID");
+            mOutEntry->parent.ident = util::hostToDevice32(parentRef.id.value().id);
         }
 
         // Sort the style.
@@ -235,7 +116,6 @@
 
         for (Style::Entry& entry : style->entries) {
             flattenEntry(&entry.key, entry.value.get());
-            flattenMetaData(&entry.key);
         }
     }
 
@@ -243,8 +123,8 @@
         for (auto& attrRef : styleable->entries) {
             BinaryPrimitive val(Res_value{});
             flattenEntry(&attrRef, &val);
-            flattenMetaData(&attrRef);
         }
+
     }
 
     void visit(Array* array) override {
@@ -253,7 +133,6 @@
             flattenValue(item.get(), outEntry);
             outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
             mEntryCount++;
-            flattenMetaData(item.get());
         }
     }
 
@@ -297,18 +176,45 @@
 
             Reference key(q);
             flattenEntry(&key, plural->values[i].get());
-            flattenMetaData(plural->values[i].get());
         }
     }
+
+    /**
+     * Call this after visiting a Value. This will finish any work that
+     * needs to be done to prepare the entry.
+     */
+    void finish() {
+        mOutEntry->count = util::hostToDevice32(mEntryCount);
+    }
+
+private:
+    void flattenKey(Reference* key, ResTable_map* outEntry) {
+        assert(key->id && "key has no ID");
+        outEntry->name.ident = util::hostToDevice32(key->id.value().id);
+    }
+
+    void flattenValue(Item* value, ResTable_map* outEntry) {
+        bool result = value->flatten(&outEntry->value);
+        assert(result && "flatten failed");
+    }
+
+    void flattenEntry(Reference* key, Item* value) {
+        ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>();
+        flattenKey(key, outEntry);
+        flattenValue(value, outEntry);
+        outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
+        mEntryCount++;
+    }
+
+    ResTable_entry_ext* mOutEntry;
+    BigBuffer* mBuffer;
+    size_t mEntryCount = 0;
 };
 
 class PackageFlattener {
 public:
-    PackageFlattener(IDiagnostics* diag, TableFlattenerOptions options,
-                     ResourceTablePackage* package, SymbolWriter* symbolWriter,
-                     StringPool* sourcePool) :
-            mDiag(diag), mOptions(options), mPackage(package), mSymbols(symbolWriter),
-            mSourcePool(sourcePool) {
+    PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) :
+            mDiag(diag), mPackage(package) {
     }
 
     bool flattenPackage(BigBuffer* buffer) {
@@ -337,9 +243,6 @@
         pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size());
         StringPool::flattenUtf16(pkgWriter.getBuffer(), mKeyPool);
 
-        // Add the ResTable_package header/type/key strings to the offset.
-        mSymbols->shiftAllOffsets(pkgWriter.size());
-
         // Append the types.
         buffer->appendBuffer(std::move(typeBuffer));
 
@@ -349,12 +252,9 @@
 
 private:
     IDiagnostics* mDiag;
-    TableFlattenerOptions mOptions;
     ResourceTablePackage* mPackage;
     StringPool mTypePool;
     StringPool mKeyPool;
-    SymbolWriter* mSymbols;
-    StringPool* mSourcePool;
 
     template <typename T, bool IsItem>
     T* writeEntry(FlatEntry* entry, BigBuffer* buffer) {
@@ -376,62 +276,24 @@
             outEntry->flags |= ResTable_entry::FLAG_COMPLEX;
         }
 
-        outEntry->key.index = util::hostToDevice32(entry->entryKey);
-        outEntry->size = sizeof(T);
-
-        if (mOptions.useExtendedChunks) {
-            // Write the extra source block. This will be ignored by the Android runtime.
-            ResTable_entry_source* sourceBlock = buffer->nextBlock<ResTable_entry_source>();
-            sourceBlock->path.index = util::hostToDevice32(entry->sourcePathKey);
-            sourceBlock->line = util::hostToDevice32(entry->sourceLine);
-            sourceBlock->comment.index = util::hostToDevice32(entry->commentKey);
-            outEntry->size += sizeof(*sourceBlock);
-        }
-
         outEntry->flags = util::hostToDevice16(outEntry->flags);
-        outEntry->size = util::hostToDevice16(outEntry->size);
+        outEntry->key.index = util::hostToDevice32(entry->entryKey);
+        outEntry->size = util::hostToDevice16(sizeof(T));
         return result;
     }
 
     bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
         if (Item* item = valueCast<Item>(entry->value)) {
             writeEntry<ResTable_entry, true>(entry, buffer);
-            bool privateRef = false;
-            if (Reference* ref = valueCast<Reference>(entry->value)) {
-                // If there is no ID or the reference is private and we allow extended chunks,
-                // write out a 0 and mark the symbol table with the name of the reference.
-                privateRef = (ref->privateReference && mOptions.useExtendedChunks);
-                if (!ref->id || privateRef) {
-                    assert(ref->name && "reference must have at least a name");
-                    mSymbols->addSymbol(*ref, buffer->size() + offsetof(Res_value, data));
-                }
-            }
             Res_value* outValue = buffer->nextBlock<Res_value>();
             bool result = item->flatten(outValue);
             assert(result && "flatten failed");
-            if (privateRef) {
-                // Force the value of 0 so we look up the symbol at unflatten time.
-                outValue->data = 0;
-            }
             outValue->size = util::hostToDevice16(sizeof(*outValue));
         } else {
-            const size_t beforeEntry = buffer->size();
             ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
-            MapFlattenVisitor visitor(mSymbols, entry, buffer, mSourcePool, mSourcePool,
-                                      mOptions.useExtendedChunks);
+            MapFlattenVisitor visitor(outEntry, buffer);
             entry->value->accept(&visitor);
-            outEntry->count = util::hostToDevice32(visitor.mEntryCount);
-            if (visitor.mParent) {
-                const bool forceSymbol = visitor.mParent->privateReference &&
-                        mOptions.useExtendedChunks;
-                if (!visitor.mParent->id || forceSymbol) {
-                    assert(visitor.mParent->name && "reference must have a name");
-                    mSymbols->addSymbol(*visitor.mParent,
-                                        beforeEntry + offsetof(ResTable_entry_ext, parent));
-                } else {
-                    outEntry->parent.ident = util::hostToDevice32(visitor.mParent->id.value().id);
-                }
-            }
+            visitor.finish();
         }
         return true;
     }
@@ -480,7 +342,7 @@
     std::vector<ResourceTableType*> collectAndSortTypes() {
         std::vector<ResourceTableType*> sortedTypes;
         for (auto& type : mPackage->types) {
-            if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
+            if (type->type == ResourceType::kStyleable) {
                 // Styleables aren't real Resource Types, they are represented in the R.java
                 // file.
                 continue;
@@ -551,52 +413,6 @@
         return true;
     }
 
-    bool flattenPublic(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries,
-                       BigBuffer* buffer) {
-        ChunkWriter publicWriter(buffer);
-        Public_header* publicHeader = publicWriter.startChunk<Public_header>(RES_TABLE_PUBLIC_TYPE);
-        publicHeader->typeId = type->id.value();
-
-        for (ResourceEntry* entry : *sortedEntries) {
-            if (entry->symbolStatus.state != SymbolState::kUndefined) {
-                // Write the public status of this entry.
-                Public_entry* publicEntry = publicWriter.nextBlock<Public_entry>();
-                publicEntry->entryId = util::hostToDevice32(entry->id.value());
-                publicEntry->key.index = util::hostToDevice32(mKeyPool.makeRef(
-                        entry->name).getIndex());
-                publicEntry->source.path.index = util::hostToDevice32(mSourcePool->makeRef(
-                        util::utf8ToUtf16(entry->symbolStatus.source.path)).getIndex());
-                if (entry->symbolStatus.source.line) {
-                    publicEntry->source.line = util::hostToDevice32(
-                            entry->symbolStatus.source.line.value());
-                }
-                publicEntry->source.comment.index = util::hostToDevice32(mSourcePool->makeRef(
-                        entry->symbolStatus.comment).getIndex());
-
-                switch (entry->symbolStatus.state) {
-                case SymbolState::kPrivate:
-                    publicEntry->state = Public_entry::kPrivate;
-                    break;
-
-                case SymbolState::kPublic:
-                    publicEntry->state = Public_entry::kPublic;
-                    break;
-
-                case SymbolState::kUndefined:
-                    publicEntry->state = Public_entry::kUndefined;
-                    break;
-                }
-
-                // Don't hostToDevice until the last step.
-                publicHeader->count += 1;
-            }
-        }
-
-        publicHeader->count = util::hostToDevice32(publicHeader->count);
-        publicWriter.finish();
-        return true;
-    }
-
     bool flattenTypes(BigBuffer* buffer) {
         // Sort the types by their IDs. They will be inserted into the StringPool in this order.
         std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes();
@@ -620,12 +436,6 @@
                 return false;
             }
 
-            if (mOptions.useExtendedChunks) {
-                if (!flattenPublic(type, &sortedEntries, buffer)) {
-                    return false;
-                }
-            }
-
             // The binary resource table lists resource entries for each configuration.
             // We store them inverted, where a resource entry lists the values for each
             // configuration available. Here we reverse this to match the binary table.
@@ -635,26 +445,8 @@
 
                 // Group values by configuration.
                 for (auto& configValue : entry->values) {
-                    Value* value = configValue.value.get();
-
-                    const StringPool::Ref sourceRef = mSourcePool->makeRef(
-                            util::utf8ToUtf16(value->getSource().path));
-
-                    uint32_t lineNumber = 0;
-                    if (value->getSource().line) {
-                        lineNumber = value->getSource().line.value();
-                    }
-
-                    const StringPool::Ref commentRef = mSourcePool->makeRef(value->getComment());
-
-                    configToEntryListMap[configValue.config]
-                            .push_back(FlatEntry{
-                                    entry,
-                                    value,
-                                    keyIndex,
-                                    (uint32_t) sourceRef.getIndex(),
-                                    lineNumber,
-                                    (uint32_t) commentRef.getIndex() });
+                    configToEntryListMap[configValue.config].push_back(FlatEntry{
+                            entry, configValue.value.get(), keyIndex });
                 }
             }
 
@@ -692,86 +484,18 @@
     // Flatten the values string pool.
     StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool);
 
-    // If we have a reference to a symbol that doesn't exist, we don't know its resource ID.
-    // We encode the name of the symbol along with the offset of where to include the resource ID
-    // once it is found.
-    StringPool symbolPool;
-    std::vector<SymbolWriter::Entry> symbolOffsets;
-
-    // String pool holding the source paths of each value.
-    StringPool sourcePool;
-
     BigBuffer packageBuffer(1024);
 
     // Flatten each package.
     for (auto& package : table->packages) {
-        const size_t beforePackageSize = packageBuffer.size();
-
-        // All packages will share a single global symbol pool.
-        SymbolWriter packageSymbolWriter(&symbolPool);
-
-        PackageFlattener flattener(context->getDiagnostics(), mOptions, package.get(),
-                                   &packageSymbolWriter, &sourcePool);
+        PackageFlattener flattener(context->getDiagnostics(), package.get());
         if (!flattener.flattenPackage(&packageBuffer)) {
             return false;
         }
-
-        // The symbols are offset only from their own Package start. Offset them from the
-        // start of the packageBuffer.
-        packageSymbolWriter.shiftAllOffsets(beforePackageSize);
-
-        // Extract all the symbols to offset
-        symbolOffsets.insert(symbolOffsets.end(),
-                             std::make_move_iterator(packageSymbolWriter.symbols.begin()),
-                             std::make_move_iterator(packageSymbolWriter.symbols.end()));
     }
 
-    SymbolTable_entry* symbolEntryData = nullptr;
-    if (mOptions.useExtendedChunks) {
-        if (!symbolOffsets.empty()) {
-            // Sort the offsets so we can scan them linearly.
-            std::sort(symbolOffsets.begin(), symbolOffsets.end(),
-                      [](const SymbolWriter::Entry& a, const SymbolWriter::Entry& b) -> bool {
-                          return a.offset < b.offset;
-                      });
-
-            // Write the Symbol header.
-            ChunkWriter symbolWriter(tableWriter.getBuffer());
-            SymbolTable_header* symbolHeader = symbolWriter.startChunk<SymbolTable_header>(
-                    RES_TABLE_SYMBOL_TABLE_TYPE);
-            symbolHeader->count = util::hostToDevice32(symbolOffsets.size());
-
-            symbolEntryData = symbolWriter.nextBlock<SymbolTable_entry>(symbolOffsets.size());
-            StringPool::flattenUtf8(symbolWriter.getBuffer(), symbolPool);
-            symbolWriter.finish();
-        }
-
-        if (sourcePool.size() > 0) {
-            // Write out source pool.
-            ChunkWriter srcWriter(tableWriter.getBuffer());
-            srcWriter.startChunk<ResChunk_header>(RES_TABLE_SOURCE_POOL_TYPE);
-            StringPool::flattenUtf8(srcWriter.getBuffer(), sourcePool);
-            srcWriter.finish();
-        }
-    }
-
-    const size_t beforePackagesSize = tableWriter.size();
-
     // Finally merge all the packages into the main buffer.
     tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer));
-
-    // Update the offsets to their final values.
-    if (symbolEntryData) {
-        for (SymbolWriter::Entry& entry : symbolOffsets) {
-            symbolEntryData->name.index = util::hostToDevice32(entry.name.getIndex());
-
-            // The symbols were all calculated with the packageBuffer offset. We need to
-            // add the beginning of the output buffer.
-            symbolEntryData->offset = util::hostToDevice32(entry.offset + beforePackagesSize);
-            symbolEntryData++;
-        }
-    }
-
     tableWriter.finish();
     return true;
 }
diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h
index 901b129..0ab0197 100644
--- a/tools/aapt2/flatten/TableFlattener.h
+++ b/tools/aapt2/flatten/TableFlattener.h
@@ -24,28 +24,15 @@
 class BigBuffer;
 class ResourceTable;
 
-struct TableFlattenerOptions {
-    /**
-     * Specifies whether to output extended chunks, like
-     * source information and missing symbol entries. Default
-     * is false.
-     *
-     * Set this to true when emitting intermediate resource table.
-     */
-    bool useExtendedChunks = false;
-};
-
 class TableFlattener : public IResourceTableConsumer {
 public:
-    TableFlattener(BigBuffer* buffer, TableFlattenerOptions options) :
-            mBuffer(buffer), mOptions(options) {
+    TableFlattener(BigBuffer* buffer) : mBuffer(buffer) {
     }
 
     bool consume(IAaptContext* context, ResourceTable* table) override;
 
 private:
     BigBuffer* mBuffer;
-    TableFlattenerOptions mOptions;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index 7030603..39c4fd3 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -38,9 +38,7 @@
 
     ::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) {
         BigBuffer buffer(1024);
-        TableFlattenerOptions options = {};
-        options.useExtendedChunks = true;
-        TableFlattener flattener(&buffer, options);
+        TableFlattener flattener(&buffer);
         if (!flattener.consume(mContext.get(), table)) {
             return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
         }
@@ -54,9 +52,7 @@
 
     ::testing::AssertionResult flatten(ResourceTable* table, ResourceTable* outTable) {
         BigBuffer buffer(1024);
-        TableFlattenerOptions options = {};
-        options.useExtendedChunks = true;
-        TableFlattener flattener(&buffer, options);
+        TableFlattener flattener(&buffer);
         if (!flattener.consume(mContext.get(), table)) {
             return ::testing::AssertionFailure() << "failed to flatten ResourceTable";
         }
@@ -210,58 +206,6 @@
                            Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
 }
 
-TEST_F(TableFlattenerTest, FlattenUnlinkedTable) {
-    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
-            .setPackageId(u"com.app.test", 0x7f)
-            .addValue(u"@com.app.test:integer/one", ResourceId(0x7f020000),
-                      test::buildReference(u"@android:integer/foo"))
-            .addValue(u"@com.app.test:style/Theme", ResourceId(0x7f030000), test::StyleBuilder()
-                    .setParent(u"@android:style/Theme.Material")
-                    .addItem(u"@android:attr/background", {})
-                    .addItem(u"@android:attr/colorAccent",
-                             test::buildReference(u"@com.app.test:color/green"))
-                    .build())
-            .build();
-
-    {
-        // Need access to stringPool to make RawString.
-        Style* style = test::getValue<Style>(table.get(), u"@com.app.test:style/Theme");
-        style->entries[0].value = util::make_unique<RawString>(table->stringPool.makeRef(u"foo"));
-    }
-
-    ResourceTable finalTable;
-    ASSERT_TRUE(flatten(table.get(), &finalTable));
-
-    Reference* ref = test::getValue<Reference>(&finalTable, u"@com.app.test:integer/one");
-    ASSERT_NE(ref, nullptr);
-    AAPT_ASSERT_TRUE(ref->name);
-    EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@android:integer/foo"));
-
-    Style* style = test::getValue<Style>(&finalTable, u"@com.app.test:style/Theme");
-    ASSERT_NE(style, nullptr);
-    AAPT_ASSERT_TRUE(style->parent);
-    AAPT_ASSERT_TRUE(style->parent.value().name);
-    EXPECT_EQ(style->parent.value().name.value(),
-              test::parseNameOrDie(u"@android:style/Theme.Material"));
-
-    ASSERT_EQ(2u, style->entries.size());
-
-    AAPT_ASSERT_TRUE(style->entries[0].key.name);
-    EXPECT_EQ(style->entries[0].key.name.value(),
-              test::parseNameOrDie(u"@android:attr/background"));
-    RawString* raw = valueCast<RawString>(style->entries[0].value.get());
-    ASSERT_NE(raw, nullptr);
-    EXPECT_EQ(*raw->value, u"foo");
-
-    AAPT_ASSERT_TRUE(style->entries[1].key.name);
-    EXPECT_EQ(style->entries[1].key.name.value(),
-              test::parseNameOrDie(u"@android:attr/colorAccent"));
-    ref = valueCast<Reference>(style->entries[1].value.get());
-    ASSERT_NE(ref, nullptr);
-    AAPT_ASSERT_TRUE(ref->name);
-    EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@com.app.test:color/green"));
-}
-
 TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
     Attribute attr(false);
     attr.typeMask = android::ResTable_map::TYPE_INTEGER;
@@ -284,33 +228,4 @@
     EXPECT_EQ(attr.maxInt, actualAttr->maxInt);
 }
 
-TEST_F(TableFlattenerTest, FlattenSourceAndCommentsForChildrenOfCompoundValues) {
-    Style style;
-    Reference key(test::parseNameOrDie(u"@android:attr/foo"));
-    key.id = ResourceId(0x01010000);
-    key.setSource(Source("test").withLine(2));
-    key.setComment(StringPiece16(u"comment"));
-    style.entries.push_back(Style::Entry{ key, util::make_unique<Id>() });
-
-    test::ResourceTableBuilder builder = test::ResourceTableBuilder();
-    std::unique_ptr<ResourceTable> table = builder
-            .setPackageId(u"android", 0x01)
-            .addValue(u"@android:attr/foo", ResourceId(0x01010000),
-                      test::AttributeBuilder().build())
-            .addValue(u"@android:style/foo", ResourceId(0x01020000),
-                      std::unique_ptr<Style>(style.clone(builder.getStringPool())))
-            .build();
-
-    ResourceTable result;
-    ASSERT_TRUE(flatten(table.get(), &result));
-
-    Style* actualStyle = test::getValue<Style>(&result, u"@android:style/foo");
-    ASSERT_NE(nullptr, actualStyle);
-    ASSERT_EQ(1u, actualStyle->entries.size());
-
-    Reference* actualKey = &actualStyle->entries[0].key;
-    EXPECT_EQ(key.getSource(), actualKey->getSource());
-    EXPECT_EQ(key.getComment(), actualKey->getComment());
-}
-
 } // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index fd76e88..8e32179 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -19,6 +19,7 @@
 #include "Flags.h"
 #include "Locale.h"
 #include "NameMangler.h"
+#include "ResourceUtils.h"
 #include "compile/IdAssigner.h"
 #include "filter/ConfigFilter.h"
 #include "flatten/Archive.h"
@@ -35,12 +36,14 @@
 #include "link/TableMerger.h"
 #include "process/IResourceTableConsumer.h"
 #include "process/SymbolTable.h"
+#include "proto/ProtoSerialize.h"
 #include "unflatten/BinaryResourceParser.h"
-#include "unflatten/FileExportHeaderReader.h"
 #include "util/Files.h"
 #include "util/StringPiece.h"
 #include "xml/XmlDom.h"
 
+#include <google/protobuf/io/coded_stream.h>
+
 #include <fstream>
 #include <sys/stat.h>
 #include <vector>
@@ -144,6 +147,22 @@
         return table;
     }
 
+    std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
+                                                   const void* data, size_t len) {
+        pb::ResourceTable pbTable;
+        if (!pbTable.ParseFromArray(data, len)) {
+            mContext->getDiagnostics()->error(DiagMessage(source) << "invalid compiled table");
+            return {};
+        }
+
+        std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source,
+                                                                      mContext->getDiagnostics());
+        if (!table) {
+            return {};
+        }
+        return table;
+    }
+
     /**
      * Inflates an XML file from the source path.
      */
@@ -161,18 +180,16 @@
             const Source& source,
             const void* data, size_t len,
             IDiagnostics* diag) {
-        std::string errorStr;
-        ssize_t offset = getWrappedDataOffset(data, len, &errorStr);
-        if (offset < 0) {
-            diag->error(DiagMessage(source) << errorStr);
+        CompiledFileInputStream inputStream(data, len);
+        if (!inputStream.CompiledFile()) {
+            diag->error(DiagMessage(source) << "invalid compiled file header");
             return {};
         }
 
-        std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
-                reinterpret_cast<const uint8_t*>(data) + static_cast<size_t>(offset),
-                len - static_cast<size_t>(offset),
-                diag,
-                source);
+        const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
+        const size_t xmlDataLen = inputStream.size();
+
+        std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
         if (!xmlRes) {
             return {};
         }
@@ -182,11 +199,16 @@
     static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
                                                               const void* data, size_t len,
                                                               IDiagnostics* diag) {
-        std::unique_ptr<ResourceFile> resFile = util::make_unique<ResourceFile>();
-        std::string errorStr;
-        ssize_t offset = unwrapFileExportHeader(data, len, resFile.get(), &errorStr);
-        if (offset < 0) {
-            diag->error(DiagMessage(source) << errorStr);
+        CompiledFileInputStream inputStream(data, len);
+        const pb::CompiledFile* pbFile = inputStream.CompiledFile();
+        if (!pbFile) {
+            diag->error(DiagMessage(source) << "invalid compiled file header");
+            return {};
+        }
+
+        std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source,
+                                                                              diag);
+        if (!resFile) {
             return {};
         }
         return resFile;
@@ -214,16 +236,16 @@
             return false;
         }
 
-        std::string errorStr;
-        ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr);
-        if (offset < 0) {
-            mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
+        CompiledFileInputStream inputStream(data->data(), data->size());
+        if (!inputStream.CompiledFile()) {
+            mContext->getDiagnostics()->error(DiagMessage(file->getSource())
+                                              << "invalid compiled file header");
             return false;
         }
 
         if (writer->startEntry(outPath, getCompressionFlags(outPath))) {
-            if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset,
-                                   data->size() - static_cast<size_t>(offset))) {
+            if (writer->writeEntry(reinterpret_cast<const uint8_t*>(inputStream.data()),
+                                   inputStream.size())) {
                 if (writer->finishEntry()) {
                     return true;
                 }
@@ -307,9 +329,7 @@
 
     bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
         BigBuffer buffer(1024);
-        TableFlattenerOptions options = {};
-        options.useExtendedChunks = mOptions.staticLib;
-        TableFlattener flattener(&buffer, options);
+        TableFlattener flattener(&buffer);
         if (!flattener.consume(mContext, table)) {
             return false;
         }
@@ -445,8 +465,8 @@
             return false;
         }
 
-        std::unique_ptr<ResourceTable> table = loadTable(file->getSource(), data->data(),
-                                                         data->size());
+        std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(), data->data(),
+                                                               data->size());
         if (!table) {
             return false;
         }
diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp
new file mode 100644
index 0000000..99981c5
--- /dev/null
+++ b/tools/aapt2/proto/ProtoHelpers.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "proto/ProtoHelpers.h"
+
+namespace aapt {
+
+void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool) {
+    BigBuffer buffer(1024);
+    StringPool::flattenUtf8(&buffer, pool);
+
+    std::string* data = outPbPool->mutable_data();
+    data->reserve(buffer.size());
+
+    size_t offset = 0;
+    for (const BigBuffer::Block& block : buffer) {
+        data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size);
+        offset += block.size;
+    }
+}
+
+void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource) {
+    StringPool::Ref ref = srcPool->makeRef(util::utf8ToUtf16(source.path));
+    outPbSource->set_path_idx(static_cast<uint32_t>(ref.getIndex()));
+    if (source.line) {
+        outPbSource->set_line_no(static_cast<uint32_t>(source.line.value()));
+    }
+}
+
+void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool,
+                             Source* outSource) {
+    if (pbSource.has_path_idx()) {
+        outSource->path = util::getString8(srcPool, pbSource.path_idx()).toString();
+    }
+
+    if (pbSource.has_line_no()) {
+        outSource->line = static_cast<size_t>(pbSource.line_no());
+    }
+}
+
+pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state) {
+    switch (state) {
+    case SymbolState::kPrivate: return pb::SymbolStatus_Visibility_Private;
+    case SymbolState::kPublic: return pb::SymbolStatus_Visibility_Public;
+    default: break;
+    }
+    return pb::SymbolStatus_Visibility_Unknown;
+}
+
+SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility) {
+    switch (pbVisibility) {
+    case pb::SymbolStatus_Visibility_Private: return SymbolState::kPrivate;
+    case pb::SymbolStatus_Visibility_Public: return SymbolState::kPublic;
+    default: break;
+    }
+    return SymbolState::kUndefined;
+}
+
+void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig) {
+    android::ResTable_config flatConfig = config;
+    flatConfig.size = sizeof(flatConfig);
+    flatConfig.swapHtoD();
+    outPbConfig->set_data(&flatConfig, sizeof(flatConfig));
+}
+
+bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig,
+                                        ConfigDescription* outConfig) {
+    if (!pbConfig.has_data()) {
+        return false;
+    }
+
+    const android::ResTable_config* config;
+    if (pbConfig.data().size() > sizeof(*config)) {
+        return false;
+    }
+
+    config = reinterpret_cast<const android::ResTable_config*>(pbConfig.data().data());
+    outConfig->copyFromDtoH(*config);
+    return true;
+}
+
+pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type) {
+    switch (type) {
+    case Reference::Type::kResource:  return pb::Reference_Type_Ref;
+    case Reference::Type::kAttribute: return pb::Reference_Type_Attr;
+    default: break;
+    }
+    return pb::Reference_Type_Ref;
+}
+
+Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType) {
+    switch (pbType) {
+    case pb::Reference_Type_Ref:  return Reference::Type::kResource;
+    case pb::Reference_Type_Attr: return Reference::Type::kAttribute;
+    default: break;
+    }
+    return Reference::Type::kResource;
+}
+
+pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx) {
+    switch (pluralIdx) {
+    case Plural::Zero:  return pb::Plural_Arity_Zero;
+    case Plural::One:   return pb::Plural_Arity_One;
+    case Plural::Two:   return pb::Plural_Arity_Two;
+    case Plural::Few:   return pb::Plural_Arity_Few;
+    case Plural::Many:  return pb::Plural_Arity_Many;
+    default: break;
+    }
+    return pb::Plural_Arity_Other;
+}
+
+size_t deserializePluralEnumFromPb(pb::Plural_Arity arity) {
+    switch (arity) {
+    case pb::Plural_Arity_Zero: return Plural::Zero;
+    case pb::Plural_Arity_One:  return Plural::One;
+    case pb::Plural_Arity_Two:  return Plural::Two;
+    case pb::Plural_Arity_Few:  return Plural::Few;
+    case pb::Plural_Arity_Many: return Plural::Many;
+    default: break;
+    }
+    return Plural::Other;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h
new file mode 100644
index 0000000..02e67f1
--- /dev/null
+++ b/tools/aapt2/proto/ProtoHelpers.h
@@ -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.
+ */
+
+#ifndef AAPT_PROTO_PROTOHELPERS_H
+#define AAPT_PROTO_PROTOHELPERS_H
+
+#include "ConfigDescription.h"
+#include "ResourceTable.h"
+#include "Source.h"
+#include "StringPool.h"
+
+#include "proto/frameworks/base/tools/aapt2/Format.pb.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool);
+
+void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource);
+void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool,
+                             Source* outSource);
+
+pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state);
+SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility);
+
+void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig);
+bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig,
+                                        ConfigDescription* outConfig);
+
+pb::Reference_Type serializeReferenceTypeToPb(Reference::Type type);
+Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType);
+
+pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx);
+size_t deserializePluralEnumFromPb(pb::Plural_Arity arity);
+
+} // namespace aapt
+
+#endif /* AAPT_PROTO_PROTOHELPERS_H */
diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h
new file mode 100644
index 0000000..6e224ab
--- /dev/null
+++ b/tools/aapt2/proto/ProtoSerialize.h
@@ -0,0 +1,77 @@
+/*
+ * 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 AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
+#define AAPT_FLATTEN_TABLEPROTOSERIALIZER_H
+
+#include "Diagnostics.h"
+#include "ResourceTable.h"
+#include "Source.h"
+#include "proto/ProtoHelpers.h"
+
+#include <android-base/macros.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+
+namespace aapt {
+
+std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table);
+std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable,
+                                                      const Source& source,
+                                                      IDiagnostics* diag);
+
+std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file);
+std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile,
+                                                            const Source& source,
+                                                            IDiagnostics* diag);
+
+class CompiledFileOutputStream : public google::protobuf::io::CopyingOutputStream {
+public:
+    CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out,
+                             pb::CompiledFile* pbFile);
+    bool Write(const void* data, int size) override;
+    bool Finish();
+
+private:
+    bool ensureFileWritten();
+
+    google::protobuf::io::CodedOutputStream mOut;
+    pb::CompiledFile* mPbFile;
+
+    DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream);
+};
+
+class CompiledFileInputStream {
+public:
+    CompiledFileInputStream(const void* data, size_t size);
+
+    const pb::CompiledFile* CompiledFile();
+
+    const void* data();
+    size_t size();
+
+private:
+    google::protobuf::io::CodedInputStream mIn;
+    std::unique_ptr<pb::CompiledFile> mPbFile;
+    const uint8_t* mData;
+    size_t mSize;
+
+    DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
new file mode 100644
index 0000000..1310aa6
--- /dev/null
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -0,0 +1,514 @@
+/*
+ * 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 "ResourceTable.h"
+#include "ResourceUtils.h"
+#include "ValueVisitor.h"
+#include "proto/ProtoHelpers.h"
+#include "proto/ProtoSerialize.h"
+#include "util/Comparators.h"
+
+#include <androidfw/ResourceTypes.h>
+
+namespace aapt {
+
+namespace {
+
+class ReferenceIdToNameVisitor : public ValueVisitor {
+public:
+    using ValueVisitor::visit;
+
+    ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceNameRef>* mapping) :
+            mMapping(mapping) {
+        assert(mMapping);
+    }
+
+    void visit(Reference* reference) override {
+        if (!reference->id || !reference->id.value().isValid()) {
+            return;
+        }
+
+        ResourceId id = reference->id.value();
+        auto cacheIter = mMapping->find(id);
+        if (cacheIter != mMapping->end()) {
+            reference->name = cacheIter->second.toResourceName();
+        }
+    }
+
+private:
+    const std::map<ResourceId, ResourceNameRef>* mMapping;
+};
+
+class PackagePbDeserializer {
+public:
+    PackagePbDeserializer(const android::ResStringPool* valuePool,
+                          const android::ResStringPool* sourcePool,
+                          const android::ResStringPool* symbolPool,
+                          const Source& source, IDiagnostics* diag) :
+            mValuePool(valuePool), mSourcePool(sourcePool), mSymbolPool(symbolPool),
+            mSource(source), mDiag(diag) {
+    }
+
+public:
+    bool deserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) {
+        Maybe<uint8_t> id;
+        if (pbPackage.has_package_id()) {
+            id = static_cast<uint8_t>(pbPackage.package_id());
+        }
+
+        std::map<ResourceId, ResourceNameRef> idIndex;
+
+        ResourceTablePackage* pkg = table->createPackage(
+                util::utf8ToUtf16(pbPackage.package_name()), id);
+        for (const pb::Type& pbType : pbPackage.types()) {
+            const ResourceType* resType = parseResourceType(util::utf8ToUtf16(pbType.name()));
+            if (!resType) {
+                mDiag->error(DiagMessage(mSource) << "unknown type '" << pbType.name() << "'");
+                return {};
+            }
+
+            ResourceTableType* type = pkg->findOrCreateType(*resType);
+
+            for (const pb::Entry& pbEntry : pbType.entries()) {
+                ResourceEntry* entry = type->findOrCreateEntry(util::utf8ToUtf16(pbEntry.name()));
+
+                // Deserialize the symbol status (public/private with source and comments).
+                if (pbEntry.has_symbol_status()) {
+                    const pb::SymbolStatus& pbStatus = pbEntry.symbol_status();
+                    if (pbStatus.has_source()) {
+                        deserializeSourceFromPb(pbStatus.source(), *mSourcePool,
+                                                &entry->symbolStatus.source);
+                    }
+
+                    if (pbStatus.has_comment()) {
+                        entry->symbolStatus.comment = util::utf8ToUtf16(pbStatus.comment());
+                    }
+
+                    SymbolState visibility = deserializeVisibilityFromPb(pbStatus.visibility());
+                    entry->symbolStatus.state = visibility;
+
+                    if (visibility == SymbolState::kPublic) {
+                        // This is a public symbol, we must encode the ID now if there is one.
+                        if (pbEntry.has_id()) {
+                            entry->id = static_cast<uint16_t>(pbEntry.id());
+                        }
+
+                        if (type->symbolStatus.state != SymbolState::kPublic) {
+                            // If the type has not been made public, do so now.
+                            type->symbolStatus.state = SymbolState::kPublic;
+                            if (pbType.has_id()) {
+                                type->id = static_cast<uint8_t>(pbType.id());
+                            }
+                        }
+                    } else if (visibility == SymbolState::kPrivate) {
+                        if (type->symbolStatus.state == SymbolState::kUndefined) {
+                            type->symbolStatus.state = SymbolState::kPrivate;
+                        }
+                    }
+                }
+
+                ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id());
+                if (resId.isValid()) {
+                    idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name);
+                }
+
+                for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) {
+                    const pb::ConfigDescription& pbConfig = pbConfigValue.config();
+
+                    ConfigDescription config;
+                    if (!deserializeConfigDescriptionFromPb(pbConfig, &config)) {
+                        mDiag->error(DiagMessage(mSource) << "invalid configuration");
+                        return {};
+                    }
+
+                    auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
+                                                 config, cmp::lessThanConfig);
+                    if (iter != entry->values.end() && iter->config == config) {
+                        // Duplicate config.
+                        mDiag->error(DiagMessage(mSource) << "duplicate configuration");
+                        return {};
+                    }
+
+                    std::unique_ptr<Value> value = deserializeValueFromPb(pbConfigValue.value(),
+                                                                          config,
+                                                                          &table->stringPool);
+                    if (!value) {
+                        return {};
+                    }
+                    entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
+                }
+            }
+        }
+
+        ReferenceIdToNameVisitor visitor(&idIndex);
+        visitAllValuesInPackage(pkg, &visitor);
+        return true;
+    }
+
+private:
+    std::unique_ptr<Item> deserializeItemFromPb(const pb::Item& pbItem,
+                                                const ConfigDescription& config,
+                                                StringPool* pool) {
+        if (pbItem.has_ref()) {
+            const pb::Reference& pbRef = pbItem.ref();
+            std::unique_ptr<Reference> ref = util::make_unique<Reference>();
+            if (!deserializeReferenceFromPb(pbRef, ref.get())) {
+                return {};
+            }
+            return std::move(ref);
+
+        } else if (pbItem.has_prim()) {
+            const pb::Primitive& pbPrim = pbItem.prim();
+            android::Res_value prim = {};
+            prim.dataType = static_cast<uint8_t>(pbPrim.type());
+            prim.data = pbPrim.data();
+            return util::make_unique<BinaryPrimitive>(prim);
+
+        } else if (pbItem.has_id()) {
+            return util::make_unique<Id>();
+
+        } else if (pbItem.has_str()) {
+            const uint32_t idx = pbItem.str().idx();
+            StringPiece16 str = util::getString(*mValuePool, idx);
+
+            const android::ResStringPool_span* spans = mValuePool->styleAt(idx);
+            if (spans && spans->name.index != android::ResStringPool_span::END) {
+                StyleString styleStr = { str.toString() };
+                while (spans->name.index != android::ResStringPool_span::END) {
+                    styleStr.spans.push_back(Span{
+                            util::getString(*mValuePool, spans->name.index).toString(),
+                            spans->firstChar,
+                            spans->lastChar
+                    });
+                    spans++;
+                }
+                return util::make_unique<StyledString>(
+                        pool->makeRef(styleStr, StringPool::Context{ 1, config }));
+            }
+            return util::make_unique<String>(
+                    pool->makeRef(str, StringPool::Context{ 1, config }));
+
+        } else if (pbItem.has_raw_str()) {
+            const uint32_t idx = pbItem.raw_str().idx();
+            StringPiece16 str = util::getString(*mValuePool, idx);
+            return util::make_unique<RawString>(
+                    pool->makeRef(str, StringPool::Context{ 1, config }));
+
+        } else if (pbItem.has_file()) {
+            const uint32_t idx = pbItem.file().path_idx();
+            StringPiece16 str = util::getString(*mValuePool, idx);
+            return util::make_unique<FileReference>(
+                    pool->makeRef(str, StringPool::Context{ 0, config }));
+
+        } else {
+            mDiag->error(DiagMessage(mSource) << "unknown item");
+        }
+        return {};
+    }
+
+    std::unique_ptr<Value> deserializeValueFromPb(const pb::Value& pbValue,
+                                                  const ConfigDescription& config,
+                                                  StringPool* pool) {
+        const bool isWeak = pbValue.has_weak() ? pbValue.weak() : false;
+
+        std::unique_ptr<Value> value;
+        if (pbValue.has_item()) {
+            value = deserializeItemFromPb(pbValue.item(), config, pool);
+            if (!value) {
+                return {};
+            }
+
+        } else if (pbValue.has_compound_value()) {
+            const pb::CompoundValue pbCompoundValue = pbValue.compound_value();
+            if (pbCompoundValue.has_attr()) {
+                const pb::Attribute& pbAttr = pbCompoundValue.attr();
+                std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
+                attr->typeMask = pbAttr.format_flags();
+                for (const pb::Attribute_Symbol& pbSymbol : pbAttr.symbols()) {
+                    Attribute::Symbol symbol;
+                    deserializeItemCommon(pbSymbol, &symbol.symbol);
+                    if (!deserializeReferenceFromPb(pbSymbol.name(), &symbol.symbol)) {
+                        return {};
+                    }
+                    symbol.value = pbSymbol.value();
+                    attr->symbols.push_back(std::move(symbol));
+                }
+                value = std::move(attr);
+
+            } else if (pbCompoundValue.has_style()) {
+                const pb::Style& pbStyle = pbCompoundValue.style();
+                std::unique_ptr<Style> style = util::make_unique<Style>();
+                if (pbStyle.has_parent()) {
+                    style->parent = Reference();
+                    if (!deserializeReferenceFromPb(pbStyle.parent(), &style->parent.value())) {
+                        return {};
+                    }
+
+                    if (pbStyle.has_parent_source()) {
+                        Source parentSource;
+                        deserializeSourceFromPb(pbStyle.parent_source(), *mSourcePool,
+                                                &parentSource);
+                        style->parent.value().setSource(std::move(parentSource));
+                    }
+                }
+
+                for (const pb::Style_Entry& pbEntry : pbStyle.entries()) {
+                    Style::Entry entry;
+                    deserializeItemCommon(pbEntry, &entry.key);
+                    if (!deserializeReferenceFromPb(pbEntry.key(), &entry.key)) {
+                        return {};
+                    }
+
+                    entry.value = deserializeItemFromPb(pbEntry.item(), config, pool);
+                    if (!entry.value) {
+                        return {};
+                    }
+
+                    deserializeItemCommon(pbEntry, entry.value.get());
+                    style->entries.push_back(std::move(entry));
+                }
+                value = std::move(style);
+
+            } else if (pbCompoundValue.has_styleable()) {
+                const pb::Styleable& pbStyleable = pbCompoundValue.styleable();
+                std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
+                for (const pb::Styleable_Entry& pbEntry : pbStyleable.entries()) {
+                    Reference attrRef;
+                    deserializeItemCommon(pbEntry, &attrRef);
+                    deserializeReferenceFromPb(pbEntry.attr(), &attrRef);
+                    styleable->entries.push_back(std::move(attrRef));
+                }
+                value = std::move(styleable);
+
+            } else if (pbCompoundValue.has_array()) {
+                const pb::Array& pbArray = pbCompoundValue.array();
+                std::unique_ptr<Array> array = util::make_unique<Array>();
+                for (const pb::Array_Entry& pbEntry : pbArray.entries()) {
+                    std::unique_ptr<Item> item = deserializeItemFromPb(pbEntry.item(), config,
+                                                                       pool);
+                    if (!item) {
+                        return {};
+                    }
+
+                    deserializeItemCommon(pbEntry, item.get());
+                    array->items.push_back(std::move(item));
+                }
+                value = std::move(array);
+
+            } else if (pbCompoundValue.has_plural()) {
+                const pb::Plural& pbPlural = pbCompoundValue.plural();
+                std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+                for (const pb::Plural_Entry& pbEntry : pbPlural.entries()) {
+                    size_t pluralIdx = deserializePluralEnumFromPb(pbEntry.arity());
+                    plural->values[pluralIdx] = deserializeItemFromPb(pbEntry.item(), config,
+                                                                      pool);
+                    if (!plural->values[pluralIdx]) {
+                        return {};
+                    }
+
+                    deserializeItemCommon(pbEntry, plural->values[pluralIdx].get());
+                }
+                value = std::move(plural);
+
+            } else {
+                mDiag->error(DiagMessage(mSource) << "unknown compound value");
+                return {};
+            }
+        } else {
+            mDiag->error(DiagMessage(mSource) << "unknown value");
+            return {};
+        }
+
+        assert(value && "forgot to set value");
+
+        value->setWeak(isWeak);
+        deserializeItemCommon(pbValue, value.get());
+        return value;
+    }
+
+    bool deserializeReferenceFromPb(const pb::Reference& pbRef, Reference* outRef) {
+        outRef->referenceType = deserializeReferenceTypeFromPb(pbRef.type());
+        outRef->privateReference = pbRef.private_();
+
+        if (!pbRef.has_id() && !pbRef.has_symbol_idx()) {
+            return false;
+        }
+
+        if (pbRef.has_id()) {
+            outRef->id = ResourceId(pbRef.id());
+        }
+
+        if (pbRef.has_symbol_idx()) {
+            StringPiece16 strSymbol = util::getString(*mSymbolPool, pbRef.symbol_idx());
+            ResourceNameRef nameRef;
+            if (!ResourceUtils::parseResourceName(strSymbol, &nameRef, nullptr)) {
+                mDiag->error(DiagMessage(mSource) << "invalid reference name '"
+                             << strSymbol << "'");
+                return false;
+            }
+
+            outRef->name = nameRef.toResourceName();
+        }
+        return true;
+    }
+
+    template <typename T>
+    void deserializeItemCommon(const T& pbItem, Value* outValue) {
+        if (pbItem.has_source()) {
+            Source source;
+            deserializeSourceFromPb(pbItem.source(), *mSourcePool, &source);
+            outValue->setSource(std::move(source));
+        }
+
+        if (pbItem.has_comment()) {
+            outValue->setComment(util::utf8ToUtf16(pbItem.comment()));
+        }
+    }
+
+private:
+    const android::ResStringPool* mValuePool;
+    const android::ResStringPool* mSourcePool;
+    const android::ResStringPool* mSymbolPool;
+    const Source mSource;
+    IDiagnostics* mDiag;
+};
+
+} // namespace
+
+std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable,
+                                                      const Source& source,
+                                                      IDiagnostics* diag) {
+    std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
+
+    if (!pbTable.has_string_pool()) {
+        diag->error(DiagMessage(source) << "no string pool found");
+        return {};
+    }
+
+    android::ResStringPool valuePool;
+    android::status_t result = valuePool.setTo(pbTable.string_pool().data().data(),
+                                               pbTable.string_pool().data().size());
+    if (result != android::NO_ERROR) {
+        diag->error(DiagMessage(source) << "invalid string pool");
+        return {};
+    }
+
+    android::ResStringPool sourcePool;
+    if (pbTable.has_source_pool()) {
+        result = sourcePool.setTo(pbTable.source_pool().data().data(),
+                                  pbTable.source_pool().data().size());
+        if (result != android::NO_ERROR) {
+            diag->error(DiagMessage(source) << "invalid source pool");
+            return {};
+        }
+    }
+
+    android::ResStringPool symbolPool;
+    if (pbTable.has_symbol_pool()) {
+        result = symbolPool.setTo(pbTable.symbol_pool().data().data(),
+                                  pbTable.symbol_pool().data().size());
+        if (result != android::NO_ERROR) {
+            diag->error(DiagMessage(source) << "invalid symbol pool");
+            return {};
+        }
+    }
+
+    PackagePbDeserializer packagePbDeserializer(&valuePool, &sourcePool, &symbolPool, source, diag);
+    for (const pb::Package& pbPackage : pbTable.packages()) {
+        if (!packagePbDeserializer.deserializeFromPb(pbPackage, table.get())) {
+            return {};
+        }
+    }
+    return table;
+}
+
+std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile,
+                                                            const Source& source,
+                                                            IDiagnostics* diag) {
+    std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>();
+
+    ResourceNameRef nameRef;
+
+    // Need to create an lvalue here so that nameRef can point to something real.
+    std::u16string utf16Name = util::utf8ToUtf16(pbFile.resource_name());
+    if (!ResourceUtils::parseResourceName(utf16Name, &nameRef)) {
+        diag->error(DiagMessage(source) << "invalid resource name in compiled file header: "
+                    << pbFile.resource_name());
+        return {};
+    }
+    file->name = nameRef.toResourceName();
+    file->source.path = pbFile.source_path();
+    deserializeConfigDescriptionFromPb(pbFile.config(), &file->config);
+
+    for (const pb::CompiledFile_Symbol& pbSymbol : pbFile.exported_symbols()) {
+        // Need to create an lvalue here so that nameRef can point to something real.
+        utf16Name = util::utf8ToUtf16(pbSymbol.resource_name());
+        if (!ResourceUtils::parseResourceName(utf16Name, &nameRef)) {
+            diag->error(DiagMessage(source) << "invalid resource name for exported symbol in "
+                                               "compiled file header: "
+                                            << pbFile.resource_name());
+            return {};
+        }
+        file->exportedSymbols.push_back(
+                SourcedResourceName{ nameRef.toResourceName(), pbSymbol.line_no() });
+    }
+    return file;
+}
+
+CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) :
+        mIn(static_cast<const uint8_t*>(data), size), mPbFile(),
+        mData(static_cast<const uint8_t*>(data)), mSize(size) {
+}
+
+const pb::CompiledFile* CompiledFileInputStream::CompiledFile() {
+    if (!mPbFile) {
+        std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>();
+        uint64_t pbSize = 0u;
+        if (!mIn.ReadLittleEndian64(&pbSize)) {
+            return nullptr;
+        }
+        mIn.PushLimit(static_cast<int>(pbSize));
+        if (!pbFile->ParsePartialFromCodedStream(&mIn)) {
+            return nullptr;
+        }
+
+        const size_t padding = 4 - (pbSize & 0x03);
+        mData += sizeof(uint64_t) + pbSize + padding;
+        mSize -= sizeof(uint64_t) + pbSize + padding;
+        mPbFile = std::move(pbFile);
+    }
+    return mPbFile.get();
+}
+
+const void* CompiledFileInputStream::data() {
+    if (!mPbFile) {
+        if (!CompiledFile()) {
+            return nullptr;
+        }
+    }
+    return mData;
+}
+
+size_t CompiledFileInputStream::size() {
+    if (!mPbFile) {
+        if (!CompiledFile()) {
+            return 0;
+        }
+    }
+    return mSize;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
new file mode 100644
index 0000000..4a2176d
--- /dev/null
+++ b/tools/aapt2/proto/TableProtoSerializer.cpp
@@ -0,0 +1,316 @@
+/*
+ * 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 "Resource.h"
+#include "ResourceTable.h"
+#include "StringPool.h"
+#include "ValueVisitor.h"
+#include "proto/ProtoHelpers.h"
+#include "proto/ProtoSerialize.h"
+#include "util/BigBuffer.h"
+
+namespace aapt {
+
+namespace {
+
+class PbSerializerVisitor : public RawValueVisitor {
+public:
+    using RawValueVisitor::visit;
+
+    /**
+     * Constructor to use when expecting to serialize any value.
+     */
+    PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, pb::Value* outPbValue) :
+            mSourcePool(sourcePool), mSymbolPool(symbolPool), mOutPbValue(outPbValue),
+            mOutPbItem(nullptr) {
+    }
+
+    /**
+     * Constructor to use when expecting to serialize an Item.
+     */
+    PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, pb::Item* outPbItem) :
+            mSourcePool(sourcePool), mSymbolPool(symbolPool), mOutPbValue(nullptr),
+            mOutPbItem(outPbItem) {
+    }
+
+    void visit(Reference* ref) override {
+        serializeReferenceToPb(*ref, getPbItem()->mutable_ref());
+    }
+
+    void visit(String* str) override {
+        getPbItem()->mutable_str()->set_idx(str->value.getIndex());
+    }
+
+    void visit(StyledString* str) override {
+        getPbItem()->mutable_str()->set_idx(str->value.getIndex());
+    }
+
+    void visit(FileReference* file) override {
+        getPbItem()->mutable_file()->set_path_idx(file->path.getIndex());
+    }
+
+    void visit(Id* id) override {
+        getPbItem()->mutable_id();
+    }
+
+    void visit(RawString* rawStr) override {
+        getPbItem()->mutable_raw_str()->set_idx(rawStr->value.getIndex());
+    }
+
+    void visit(BinaryPrimitive* prim) override {
+        android::Res_value val = {};
+        prim->flatten(&val);
+
+        pb::Primitive* pbPrim = getPbItem()->mutable_prim();
+        pbPrim->set_type(val.dataType);
+        pbPrim->set_data(val.data);
+    }
+
+    void visitItem(Item* item) override {
+        assert(false && "unimplemented item");
+    }
+
+    void visit(Attribute* attr) override {
+        pb::Attribute* pbAttr = getPbCompoundValue()->mutable_attr();
+        pbAttr->set_format_flags(attr->typeMask);
+        pbAttr->set_min_int(attr->minInt);
+        pbAttr->set_max_int(attr->maxInt);
+
+        for (auto& symbol : attr->symbols) {
+            pb::Attribute_Symbol* pbSymbol = pbAttr->add_symbols();
+            serializeItemCommonToPb(symbol.symbol, pbSymbol);
+            serializeReferenceToPb(symbol.symbol, pbSymbol->mutable_name());
+            pbSymbol->set_value(symbol.value);
+        }
+    }
+
+    void visit(Style* style) override {
+        pb::Style* pbStyle = getPbCompoundValue()->mutable_style();
+        if (style->parent) {
+            serializeReferenceToPb(style->parent.value(), pbStyle->mutable_parent());
+            serializeSourceToPb(style->parent.value().getSource(),
+                                mSourcePool,
+                                pbStyle->mutable_parent_source());
+        }
+
+        for (Style::Entry& entry : style->entries) {
+            pb::Style_Entry* pbEntry = pbStyle->add_entries();
+            serializeReferenceToPb(entry.key, pbEntry->mutable_key());
+
+            pb::Item* pbItem = pbEntry->mutable_item();
+            serializeItemCommonToPb(*entry.value, pbEntry);
+            PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbItem);
+            entry.value->accept(&subVisitor);
+        }
+    }
+
+    void visit(Styleable* styleable) override {
+        pb::Styleable* pbStyleable = getPbCompoundValue()->mutable_styleable();
+        for (Reference& entry : styleable->entries) {
+            pb::Styleable_Entry* pbEntry = pbStyleable->add_entries();
+            serializeItemCommonToPb(entry, pbEntry);
+            serializeReferenceToPb(entry, pbEntry->mutable_attr());
+        }
+    }
+
+    void visit(Array* array) override {
+        pb::Array* pbArray = getPbCompoundValue()->mutable_array();
+        for (auto& value : array->items) {
+            pb::Array_Entry* pbEntry = pbArray->add_entries();
+            serializeItemCommonToPb(*value, pbEntry);
+            PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbEntry->mutable_item());
+            value->accept(&subVisitor);
+        }
+    }
+
+    void visit(Plural* plural) override {
+        pb::Plural* pbPlural = getPbCompoundValue()->mutable_plural();
+        const size_t count = plural->values.size();
+        for (size_t i = 0; i < count; i++) {
+            if (!plural->values[i]) {
+                // No plural value set here.
+                continue;
+            }
+
+            pb::Plural_Entry* pbEntry = pbPlural->add_entries();
+            pbEntry->set_arity(serializePluralEnumToPb(i));
+            pb::Item* pbElement = pbEntry->mutable_item();
+            serializeItemCommonToPb(*plural->values[i], pbEntry);
+            PbSerializerVisitor subVisitor(mSourcePool, mSymbolPool, pbElement);
+            plural->values[i]->accept(&subVisitor);
+        }
+    }
+
+private:
+    pb::Item* getPbItem() {
+        if (mOutPbValue) {
+            return mOutPbValue->mutable_item();
+        }
+        return mOutPbItem;
+    }
+
+    pb::CompoundValue* getPbCompoundValue() {
+        assert(mOutPbValue);
+        return mOutPbValue->mutable_compound_value();
+    }
+
+    template <typename T>
+    void serializeItemCommonToPb(const Item& item, T* pbItem) {
+        serializeSourceToPb(item.getSource(), mSourcePool, pbItem->mutable_source());
+        if (!item.getComment().empty()) {
+            pbItem->set_comment(util::utf16ToUtf8(item.getComment()));
+        }
+    }
+
+    void serializeReferenceToPb(const Reference& ref, pb::Reference* pbRef) {
+        if (ref.id) {
+            pbRef->set_id(ref.id.value().id);
+        } else if (ref.name) {
+            StringPool::Ref symbolRef = mSymbolPool->makeRef(ref.name.value().toString());
+            pbRef->set_symbol_idx(static_cast<uint32_t>(symbolRef.getIndex()));
+        }
+        pbRef->set_private_(ref.privateReference);
+        pbRef->set_type(serializeReferenceTypeToPb(ref.referenceType));
+    }
+
+    StringPool* mSourcePool;
+    StringPool* mSymbolPool;
+    pb::Value* mOutPbValue;
+    pb::Item* mOutPbItem;
+};
+
+} // namespace
+
+std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table) {
+    // We must do this before writing the resources, since the string pool IDs may change.
+    table->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
+        int diff = a.context.priority - b.context.priority;
+        if (diff < 0) return true;
+        if (diff > 0) return false;
+        diff = a.context.config.compare(b.context.config);
+        if (diff < 0) return true;
+        if (diff > 0) return false;
+        return a.value < b.value;
+    });
+    table->stringPool.prune();
+
+    std::unique_ptr<pb::ResourceTable> pbTable = util::make_unique<pb::ResourceTable>();
+    serializeStringPoolToPb(table->stringPool, pbTable->mutable_string_pool());
+
+    StringPool sourcePool, symbolPool;
+
+    for (auto& package : table->packages) {
+        pb::Package* pbPackage = pbTable->add_packages();
+        if (package->id) {
+            pbPackage->set_package_id(package->id.value());
+        }
+        pbPackage->set_package_name(util::utf16ToUtf8(package->name));
+
+        for (auto& type : package->types) {
+            pb::Type* pbType = pbPackage->add_types();
+            if (type->id) {
+                pbType->set_id(type->id.value());
+            }
+            pbType->set_name(util::utf16ToUtf8(toString(type->type)));
+
+            for (auto& entry : type->entries) {
+                pb::Entry* pbEntry = pbType->add_entries();
+                if (entry->id) {
+                    pbEntry->set_id(entry->id.value());
+                }
+                pbEntry->set_name(util::utf16ToUtf8(entry->name));
+
+                // Write the SymbolStatus struct.
+                pb::SymbolStatus* pbStatus = pbEntry->mutable_symbol_status();
+                pbStatus->set_visibility(serializeVisibilityToPb(entry->symbolStatus.state));
+                serializeSourceToPb(entry->symbolStatus.source, &sourcePool,
+                                    pbStatus->mutable_source());
+                pbStatus->set_comment(util::utf16ToUtf8(entry->symbolStatus.comment));
+
+                for (auto& configValue : entry->values) {
+                    pb::ConfigValue* pbConfigValue = pbEntry->add_config_values();
+                    serializeConfig(configValue.config, pbConfigValue->mutable_config());
+
+                    pb::Value* pbValue = pbConfigValue->mutable_value();
+                    serializeSourceToPb(configValue.value->getSource(), &sourcePool,
+                                        pbValue->mutable_source());
+                    if (!configValue.value->getComment().empty()) {
+                        pbValue->set_comment(util::utf16ToUtf8(configValue.value->getComment()));
+                    }
+
+                    if (configValue.value->isWeak()) {
+                        pbValue->set_weak(true);
+                    }
+
+                    PbSerializerVisitor visitor(&sourcePool, &symbolPool, pbValue);
+                    configValue.value->accept(&visitor);
+                }
+            }
+        }
+    }
+
+    serializeStringPoolToPb(sourcePool, pbTable->mutable_source_pool());
+    serializeStringPoolToPb(symbolPool, pbTable->mutable_symbol_pool());
+    return pbTable;
+}
+
+std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file) {
+    std::unique_ptr<pb::CompiledFile> pbFile = util::make_unique<pb::CompiledFile>();
+    pbFile->set_resource_name(util::utf16ToUtf8(file.name.toString()));
+    pbFile->set_source_path(file.source.path);
+    serializeConfig(file.config, pbFile->mutable_config());
+
+    for (const SourcedResourceName& exported : file.exportedSymbols) {
+        pb::CompiledFile_Symbol* pbSymbol = pbFile->add_exported_symbols();
+        pbSymbol->set_resource_name(util::utf16ToUtf8(exported.name.toString()));
+        pbSymbol->set_line_no(exported.line);
+    }
+    return pbFile;
+}
+
+CompiledFileOutputStream::CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out,
+                                                   pb::CompiledFile* pbFile) :
+        mOut(out), mPbFile(pbFile) {
+}
+
+bool CompiledFileOutputStream::ensureFileWritten() {
+    if (mPbFile) {
+        const uint64_t pbSize = mPbFile->ByteSize();
+        mOut.WriteLittleEndian64(pbSize);
+        mPbFile->SerializeWithCachedSizes(&mOut);
+        const size_t padding = 4 - (pbSize & 0x03);
+        if (padding > 0) {
+            uint32_t zero = 0u;
+            mOut.WriteRaw(&zero, padding);
+        }
+        mPbFile = nullptr;
+    }
+    return !mOut.HadError();
+}
+
+bool CompiledFileOutputStream::Write(const void* data, int size) {
+    if (!ensureFileWritten()) {
+        return false;
+    }
+    mOut.WriteRaw(data, size);
+    return !mOut.HadError();
+}
+
+bool CompiledFileOutputStream::Finish() {
+    return ensureFileWritten();
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp
new file mode 100644
index 0000000..1061b8f
--- /dev/null
+++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp
@@ -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.
+ */
+
+#include "ResourceTable.h"
+#include "proto/ProtoSerialize.h"
+#include "test/Builders.h"
+#include "test/Common.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(TableProtoSerializer, SerializeSinglePackage) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .setPackageId(u"com.app.a", 0x7f)
+            .addFileReference(u"@com.app.a:layout/main", ResourceId(0x7f020000),
+                              u"res/layout/main.xml")
+            .addReference(u"@com.app.a:layout/other", ResourceId(0x7f020001),
+                          u"@com.app.a:layout/main")
+            .addString(u"@com.app.a:string/text", {}, u"hi")
+            .addValue(u"@com.app.a:id/foo", {}, util::make_unique<Id>())
+            .build();
+
+    Symbol publicSymbol;
+    publicSymbol.state = SymbolState::kPublic;
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie(u"@com.app.a:layout/main"),
+                                      ResourceId(0x7f020000),
+                                      publicSymbol, context->getDiagnostics()));
+
+    Id* id = test::getValue<Id>(table.get(), u"@com.app.a:id/foo");
+    ASSERT_NE(nullptr, id);
+
+    // Make a plural.
+    std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+    plural->values[Plural::One] = util::make_unique<String>(table->stringPool.makeRef(u"one"));
+    ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:plurals/hey"),
+                                   ConfigDescription{}, std::move(plural),
+                                   context->getDiagnostics()));
+
+    std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table.get());
+    ASSERT_NE(nullptr, pbTable);
+
+    std::unique_ptr<ResourceTable> newTable = deserializeTableFromPb(*pbTable,
+                                                                     Source{ "test" },
+                                                                     context->getDiagnostics());
+    ASSERT_NE(nullptr, newTable);
+
+    Id* newId = test::getValue<Id>(newTable.get(), u"@com.app.a:id/foo");
+    ASSERT_NE(nullptr, newId);
+    EXPECT_EQ(id->isWeak(), newId->isWeak());
+
+    Maybe<ResourceTable::SearchResult> result = newTable->findResource(
+            test::parseNameOrDie(u"@com.app.a:layout/main"));
+    AAPT_ASSERT_TRUE(result);
+    EXPECT_EQ(SymbolState::kPublic, result.value().type->symbolStatus.state);
+    EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
+}
+
+TEST(TableProtoSerializer, SerializeFileHeader) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    ResourceFile f;
+    f.config = test::parseConfigOrDie("hdpi-v9");
+    f.name = test::parseNameOrDie(u"@com.app.a:layout/main");
+    f.source.path = "res/layout-hdpi-v9/main.xml";
+    f.exportedSymbols.push_back(SourcedResourceName{ test::parseNameOrDie(u"@+id/unchecked"), 23u });
+
+    const std::string expectedData = "1234";
+
+    std::unique_ptr<pb::CompiledFile> pbFile = serializeCompiledFileToPb(f);
+
+    std::string outputStr;
+    {
+        google::protobuf::io::StringOutputStream outStream(&outputStr);
+        CompiledFileOutputStream outFileStream(&outStream, pbFile.get());
+
+        ASSERT_TRUE(outFileStream.Write(expectedData.data(), expectedData.size()));
+        ASSERT_TRUE(outFileStream.Finish());
+    }
+
+    CompiledFileInputStream inFileStream(outputStr.data(), outputStr.size());
+    const pb::CompiledFile* newPbFile = inFileStream.CompiledFile();
+    ASSERT_NE(nullptr, newPbFile);
+
+    std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(*newPbFile, Source{ "test" },
+                                                                       context->getDiagnostics());
+    ASSERT_NE(nullptr, file);
+
+    std::string actualData((const char*)inFileStream.data(), inFileStream.size());
+    EXPECT_EQ(expectedData, actualData);
+    EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(inFileStream.data()) & 0x03);
+
+    ASSERT_EQ(1u, file->exportedSymbols.size());
+    EXPECT_EQ(test::parseNameOrDie(u"@+id/unchecked"), file->exportedSymbols[0].name);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 6b7a63cf..3417703 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -19,8 +19,6 @@
 #include "ResourceValues.h"
 #include "Source.h"
 #include "ValueVisitor.h"
-
-#include "flatten/ResourceTypeExtensions.h"
 #include "unflatten/BinaryResourceParser.h"
 #include "unflatten/ResChunkPullParser.h"
 #include "util/Util.h"
@@ -36,6 +34,8 @@
 
 using namespace android;
 
+namespace {
+
 /*
  * Visitor that converts a reference's resource ID to a resource name,
  * given a mapping from resource ID to resource name.
@@ -66,6 +66,8 @@
     }
 };
 
+} // namespace
+
 BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table,
                                            const Source& source, const void* data, size_t len) :
         mContext(context), mTable(table), mSource(source), mData(data), mDataLen(len) {
@@ -97,106 +99,6 @@
     return !error;
 }
 
-Maybe<Reference> BinaryResourceParser::getSymbol(const void* data) {
-    if (!mSymbolEntries || mSymbolEntryCount == 0) {
-        return {};
-    }
-
-    if ((uintptr_t) data < (uintptr_t) mData) {
-        return {};
-    }
-
-    // We only support 32 bit offsets right now.
-    const uintptr_t offset = (uintptr_t) data - (uintptr_t) mData;
-    if (offset > std::numeric_limits<uint32_t>::max()) {
-        return {};
-    }
-
-    for (size_t i = 0; i < mSymbolEntryCount; i++) {
-        if (util::deviceToHost32(mSymbolEntries[i].offset) == offset) {
-            // This offset is a symbol!
-            const StringPiece16 str = util::getString(
-                    mSymbolPool, util::deviceToHost32(mSymbolEntries[i].name.index));
-
-            ResourceNameRef nameRef;
-            bool privateRef = false;
-            if (!ResourceUtils::parseResourceName(str, &nameRef, &privateRef)) {
-                return {};
-            }
-
-            // Since we scan the symbol table in order, we can start looking for the
-            // next symbol from this point.
-            mSymbolEntryCount -= i + 1;
-            mSymbolEntries += i + 1;
-
-            Reference ref(nameRef);
-            ref.privateReference = privateRef;
-            return Maybe<Reference>(std::move(ref));
-        }
-    }
-    return {};
-}
-
-/**
- * Parses the SymbolTable_header, which is present on non-final resource tables
- * after the compile phase.
- *
- * | SymbolTable_header |
- * |--------------------|
- * |SymbolTable_entry 0 |
- * |SymbolTable_entry 1 |
- * | ...                |
- * |SymbolTable_entry n |
- * |--------------------|
- *
- */
-bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) {
-    const SymbolTable_header* header = convertTo<SymbolTable_header>(chunk);
-    if (!header) {
-        mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                          << "corrupt SymbolTable_header");
-        return false;
-    }
-
-    const uint32_t entrySizeBytes =
-            util::deviceToHost32(header->count) * sizeof(SymbolTable_entry);
-    if (entrySizeBytes > getChunkDataLen(&header->header)) {
-        mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                          << "SymbolTable_header data section too long");
-        return false;
-    }
-
-    mSymbolEntries = (const SymbolTable_entry*) getChunkData(&header->header);
-    mSymbolEntryCount = util::deviceToHost32(header->count);
-
-    // Skip over the symbol entries and parse the StringPool chunk that should be next.
-    ResChunkPullParser parser(getChunkData(&header->header) + entrySizeBytes,
-                              getChunkDataLen(&header->header) - entrySizeBytes);
-    if (!ResChunkPullParser::isGoodEvent(parser.next())) {
-        mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                          << "failed to parse chunk in SymbolTable: "
-                                          << parser.getLastError());
-        return false;
-    }
-
-    const ResChunk_header* nextChunk = parser.getChunk();
-    if (util::deviceToHost16(nextChunk->type) != android::RES_STRING_POOL_TYPE) {
-        mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                          << "expected string pool in SymbolTable but got "
-                                          << "chunk of type "
-                                          << (int) util::deviceToHost16(nextChunk->type));
-        return false;
-    }
-
-    if (mSymbolPool.setTo(nextChunk, util::deviceToHost32(nextChunk->size)) != NO_ERROR) {
-        mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                          << "corrupt string pool in SymbolTable: "
-                                          << mSymbolPool.getError());
-        return false;
-    }
-    return true;
-}
-
 /**
  * Parses the resource table, which contains all the packages, types, and entries.
  */
@@ -230,24 +132,6 @@
             }
             break;
 
-        case RES_TABLE_SYMBOL_TABLE_TYPE:
-            if (!parseSymbolTable(parser.getChunk())) {
-                return false;
-            }
-            break;
-
-        case RES_TABLE_SOURCE_POOL_TYPE: {
-            status_t err = mSourcePool.setTo(getChunkData(parser.getChunk()),
-                                             getChunkDataLen(parser.getChunk()));
-            if (err != NO_ERROR) {
-                mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                                  << "corrupt source string pool in ResTable: "
-                                                  << mSourcePool.getError());
-                return false;
-            }
-            break;
-        }
-
         case android::RES_TABLE_PACKAGE_TYPE:
             if (!parsePackage(parser.getChunk())) {
                 return false;
@@ -350,12 +234,6 @@
             }
             break;
 
-        case RES_TABLE_PUBLIC_TYPE:
-            if (!parsePublic(package, parser.getChunk())) {
-                return false;
-            }
-            break;
-
         default:
             mContext->getDiagnostics()
                     ->warn(DiagMessage(mSource)
@@ -375,97 +253,7 @@
     // Now go through the table and change local resource ID references to
     // symbolic references.
     ReferenceIdToNameVisitor visitor(&mIdIndex);
-    for (auto& package : mTable->packages) {
-        for (auto& type : package->types) {
-            for (auto& entry : type->entries) {
-                for (auto& configValue : entry->values) {
-                    configValue.value->accept(&visitor);
-                }
-            }
-        }
-    }
-    return true;
-}
-
-bool BinaryResourceParser::parsePublic(const ResourceTablePackage* package,
-                                       const ResChunk_header* chunk) {
-    const Public_header* header = convertTo<Public_header>(chunk);
-    if (!header) {
-        mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                          << "corrupt Public_header chunk");
-        return false;
-    }
-
-    if (header->typeId == 0) {
-        mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                          << "invalid type ID "
-                                          << (int) header->typeId);
-        return false;
-    }
-
-    StringPiece16 typeStr16 = util::getString(mTypePool, header->typeId - 1);
-    const ResourceType* parsedType = parseResourceType(typeStr16);
-    if (!parsedType) {
-        mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                          << "invalid type '" << typeStr16 << "'");
-        return false;
-    }
-
-    const uintptr_t chunkEnd = (uintptr_t) chunk + util::deviceToHost32(chunk->size);
-    const Public_entry* entry = (const Public_entry*) getChunkData(&header->header);
-    for (uint32_t i = 0; i < util::deviceToHost32(header->count); i++) {
-        if ((uintptr_t) entry + sizeof(*entry) > chunkEnd) {
-            mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                              << "Public_entry data section is too long");
-            return false;
-        }
-
-        const ResourceId resId(package->id.value(), header->typeId,
-                               util::deviceToHost16(entry->entryId));
-
-        const ResourceName name(package->name, *parsedType,
-                                util::getString(mKeyPool, entry->key.index).toString());
-
-        Symbol symbol;
-        if (mSourcePool.getError() == NO_ERROR) {
-            symbol.source.path = util::utf16ToUtf8(util::getString(
-                    mSourcePool, util::deviceToHost32(entry->source.path.index)));
-            symbol.source.line = util::deviceToHost32(entry->source.line);
-        }
-
-        StringPiece16 comment = util::getString(mSourcePool,
-                                                util::deviceToHost32(entry->source.comment.index));
-        if (!comment.empty()) {
-            symbol.comment = comment.toString();
-        }
-
-        switch (util::deviceToHost16(entry->state)) {
-        case Public_entry::kPrivate:
-            symbol.state = SymbolState::kPrivate;
-            break;
-
-        case Public_entry::kPublic:
-            symbol.state = SymbolState::kPublic;
-            break;
-
-        case Public_entry::kUndefined:
-            symbol.state = SymbolState::kUndefined;
-            break;
-        }
-
-        if (!mTable->setSymbolStateAllowMangled(name, resId, symbol, mContext->getDiagnostics())) {
-            return false;
-        }
-
-        // Add this resource name->id mapping to the index so
-        // that we can resolve all ID references to name references.
-        auto cacheIter = mIdIndex.find(resId);
-        if (cacheIter == mIdIndex.end()) {
-            mIdIndex.insert({ resId, name });
-        }
-
-        entry++;
-    }
+    visitAllValuesInTable(mTable, &visitor);
     return true;
 }
 
@@ -545,25 +333,12 @@
         const ResourceId resId(package->id.value(), type->id, static_cast<uint16_t>(it.index()));
 
         std::unique_ptr<Value> resourceValue;
-        const ResTable_entry_source* sourceBlock = nullptr;
-
         if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
             const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
-            if (util::deviceToHost32(mapEntry->size) - sizeof(*mapEntry) == sizeof(*sourceBlock)) {
-                const uint8_t* data = (const uint8_t*) mapEntry;
-                data += util::deviceToHost32(mapEntry->size) - sizeof(*sourceBlock);
-                sourceBlock = (const ResTable_entry_source*) data;
-            }
 
             // TODO(adamlesinski): Check that the entry count is valid.
             resourceValue = parseMapEntry(name, config, mapEntry);
         } else {
-            if (util::deviceToHost32(entry->size) - sizeof(*entry) == sizeof(*sourceBlock)) {
-                const uint8_t* data = (const uint8_t*) entry;
-                data += util::deviceToHost32(entry->size) - sizeof(*sourceBlock);
-                sourceBlock = (const ResTable_entry_source*) data;
-            }
-
             const Res_value* value = (const Res_value*)(
                     (const uint8_t*) entry + util::deviceToHost32(entry->size));
             resourceValue = parseValue(name, config, value, entry->flags);
@@ -577,30 +352,6 @@
             return false;
         }
 
-        Source source = mSource;
-        if (sourceBlock) {
-            StringPiece path = util::getString8(mSourcePool,
-                                                util::deviceToHost32(sourceBlock->path.index));
-            if (!path.empty()) {
-                source.path = path.toString();
-            }
-            source.line = util::deviceToHost32(sourceBlock->line);
-
-            if (Style* style = valueCast<Style>(resourceValue.get())) {
-                // The parent's source is the same as the resource itself, set it here.
-                if (style->parent) {
-                    style->parent.value().setSource(source);
-                }
-            }
-        }
-
-        StringPiece16 comment = util::getString(mSourcePool,
-                                                util::deviceToHost32(sourceBlock->comment.index));
-        if (!comment.empty()) {
-            resourceValue->setComment(comment);
-        }
-
-        resourceValue->setSource(source);
         if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue),
                                              mContext->getDiagnostics())) {
             return false;
@@ -674,26 +425,15 @@
         const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ?
                 Reference::Type::kResource : Reference::Type::kAttribute;
 
-        if (data != 0) {
-            // This is a normal reference.
-            return util::make_unique<Reference>(data, type);
+        if (data == 0) {
+            // A reference of 0, must be the magic @null reference.
+            Res_value nullType = {};
+            nullType.dataType = Res_value::TYPE_REFERENCE;
+            return util::make_unique<BinaryPrimitive>(nullType);
         }
 
-        // This reference has an invalid ID. Check if it is an unresolved symbol.
-        if (Maybe<Reference> ref = getSymbol(&value->data)) {
-            ref.value().referenceType = type;
-            return util::make_unique<Reference>(std::move(ref.value()));
-        }
-
-        // This is not an unresolved symbol, so it must be the magic @null reference.
-        Res_value nullType = {};
-        nullType.dataType = Res_value::TYPE_REFERENCE;
-        return util::make_unique<BinaryPrimitive>(nullType);
-    }
-
-    if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
-        return util::make_unique<RawString>(mTable->stringPool.makeRef(
-                util::getString(mValuePool, data), StringPool::Context{ 1, config }));
+        // This is a normal reference.
+        return util::make_unique<Reference>(data, type);
     }
 
     // Treat this as a raw binary primitive.
@@ -712,8 +452,6 @@
             return parseAttr(name, config, map);
         case ResourceType::kArray:
             return parseArray(name, config, map);
-        case ResourceType::kStyleable:
-            return parseStyleable(name, config, map);
         case ResourceType::kPlurals:
             return parsePlural(name, config, map);
         default:
@@ -727,51 +465,23 @@
                                                         const ConfigDescription& config,
                                                         const ResTable_map_entry* map) {
     std::unique_ptr<Style> style = util::make_unique<Style>();
-    if (util::deviceToHost32(map->parent.ident) == 0) {
-        // The parent is either not set or it is an unresolved symbol.
-        // Check to see if it is a symbol.
-        style->parent = getSymbol(&map->parent.ident);
-
-    } else {
-         // The parent is a regular reference to a resource.
+    if (util::deviceToHost32(map->parent.ident) != 0) {
+        // The parent is a regular reference to a resource.
         style->parent = Reference(util::deviceToHost32(map->parent.ident));
     }
 
     for (const ResTable_map& mapEntry : map) {
         if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
-            if (style->entries.empty()) {
-                mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                                  << "out-of-sequence meta data in style");
-                return {};
-            }
-            collectMetaData(mapEntry, &style->entries.back().key);
             continue;
         }
 
-        style->entries.emplace_back();
-        Style::Entry& styleEntry = style->entries.back();
-
-        if (util::deviceToHost32(mapEntry.name.ident) == 0) {
-            // The map entry's key (attribute) is not set. This must be
-            // a symbol reference, so resolve it.
-            Maybe<Reference> symbol = getSymbol(&mapEntry.name.ident);
-            if (!symbol) {
-                mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                                  << "unresolved style attribute");
-                return {};
-            }
-            styleEntry.key = std::move(symbol.value());
-
-        } else {
-            // The map entry's key (attribute) is a regular reference.
-            styleEntry.key.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
-        }
-
-        // Parse the attribute's value.
+        Style::Entry styleEntry;
+        styleEntry.key = Reference(util::deviceToHost32(mapEntry.name.ident));
         styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
         if (!styleEntry.value) {
             return {};
         }
+        style->entries.push_back(std::move(styleEntry));
     }
     return style;
 }
@@ -807,22 +517,7 @@
         if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
             Attribute::Symbol symbol;
             symbol.value = util::deviceToHost32(mapEntry.value.data);
-            if (util::deviceToHost32(mapEntry.name.ident) == 0) {
-                // The map entry's key (id) is not set. This must be
-                // a symbol reference, so resolve it.
-                Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
-                if (!ref) {
-                    mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                                      << "unresolved attribute symbol");
-                    return {};
-                }
-                symbol.symbol = std::move(ref.value());
-
-            } else {
-                // The map entry's key (id) is a regular reference.
-                symbol.symbol.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
-            }
-
+            symbol.symbol = Reference(util::deviceToHost32(mapEntry.name.ident));
             attr->symbols.push_back(std::move(symbol));
         }
     }
@@ -831,115 +526,26 @@
     return attr;
 }
 
-static bool isMetaDataEntry(const ResTable_map& mapEntry) {
-    switch (util::deviceToHost32(mapEntry.name.ident)) {
-    case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
-    case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
-    case ExtendedResTableMapTypes::ATTR_COMMENT:
-        return true;
-    }
-    return false;
-}
-
-bool BinaryResourceParser::collectMetaData(const ResTable_map& mapEntry, Value* value) {
-    switch (util::deviceToHost32(mapEntry.name.ident)) {
-    case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
-        value->setSource(Source(util::getString8(mSourcePool,
-                                                 util::deviceToHost32(mapEntry.value.data))));
-        return true;
-        break;
-
-    case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
-        value->setSource(value->getSource().withLine(util::deviceToHost32(mapEntry.value.data)));
-        return true;
-        break;
-
-    case ExtendedResTableMapTypes::ATTR_COMMENT:
-        value->setComment(util::getString(mSourcePool, util::deviceToHost32(mapEntry.value.data)));
-        return true;
-        break;
-    }
-    return false;
-}
-
 std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
                                                         const ConfigDescription& config,
                                                         const ResTable_map_entry* map) {
     std::unique_ptr<Array> array = util::make_unique<Array>();
-    Source source;
     for (const ResTable_map& mapEntry : map) {
-        if (isMetaDataEntry(mapEntry)) {
-            if (array->items.empty()) {
-                mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                                  << "out-of-sequence meta data in array");
-                return {};
-            }
-            collectMetaData(mapEntry, array->items.back().get());
-            continue;
-        }
-
         array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
     }
     return array;
 }
 
-std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name,
-                                                                const ConfigDescription& config,
-                                                                const ResTable_map_entry* map) {
-    std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
-    for (const ResTable_map& mapEntry : map) {
-        if (isMetaDataEntry(mapEntry)) {
-            if (styleable->entries.empty()) {
-                mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                                  << "out-of-sequence meta data in styleable");
-                return {};
-            }
-            collectMetaData(mapEntry, &styleable->entries.back());
-            continue;
-        }
-
-        if (util::deviceToHost32(mapEntry.name.ident) == 0) {
-            // The map entry's key (attribute) is not set. This must be
-            // a symbol reference, so resolve it.
-            Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
-            if (!ref) {
-                mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                                  << "unresolved styleable symbol");
-                return {};
-            }
-            styleable->entries.emplace_back(std::move(ref.value()));
-
-        } else {
-            // The map entry's key (attribute) is a regular reference.
-            styleable->entries.emplace_back(util::deviceToHost32(mapEntry.name.ident));
-        }
-    }
-    return styleable;
-}
-
 std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
                                                           const ConfigDescription& config,
                                                           const ResTable_map_entry* map) {
     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
-    Item* lastEntry = nullptr;
     for (const ResTable_map& mapEntry : map) {
-        if (isMetaDataEntry(mapEntry)) {
-            if (!lastEntry) {
-                mContext->getDiagnostics()->error(DiagMessage(mSource)
-                                                  << "out-of-sequence meta data in plural");
-                return {};
-            }
-            collectMetaData(mapEntry, lastEntry);
-            continue;
-        }
-
         std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
         if (!item) {
             return {};
         }
 
-        lastEntry = item.get();
-
         switch (util::deviceToHost32(mapEntry.name.ident)) {
             case ResTable_map::ATTR_ZERO:
                 plural->values[Plural::Zero] = std::move(item);
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 0745a59..12bc13d 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -55,14 +55,8 @@
     bool parse();
 
 private:
-    // Helper method to retrieve the symbol name for a given table offset specified
-    // as a pointer.
-    Maybe<Reference> getSymbol(const void* data);
-
     bool parseTable(const android::ResChunk_header* chunk);
-    bool parseSymbolTable(const android::ResChunk_header* chunk);
     bool parsePackage(const android::ResChunk_header* chunk);
-    bool parsePublic(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
     bool parseTypeSpec(const android::ResChunk_header* chunk);
     bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
 
@@ -87,10 +81,6 @@
                                         const ConfigDescription& config,
                                         const android::ResTable_map_entry* map);
 
-    std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name,
-                                              const ConfigDescription& config,
-                                              const android::ResTable_map_entry* map);
-
     /**
      * If the mapEntry is a special type that denotes meta data (source, comment), then it is
      * read and added to the Value.
@@ -106,23 +96,6 @@
     const void* mData;
     const size_t mDataLen;
 
-    // The array of symbol entries. Each element points to an offset
-    // in the table and an index into the symbol table string pool.
-    const SymbolTable_entry* mSymbolEntries = nullptr;
-
-    // Number of symbol entries.
-    size_t mSymbolEntryCount = 0;
-
-    // The symbol table string pool. Holds the names of symbols
-    // referenced in this table but not defined nor resolved to an
-    // ID.
-    android::ResStringPool mSymbolPool;
-
-    // The source string pool. Resource entries may have an extra
-    // field that points into this string pool, which denotes where
-    // the resource was parsed from originally.
-    android::ResStringPool mSourcePool;
-
     // The standard value string pool for resource values.
     android::ResStringPool mValuePool;
 
diff --git a/tools/aapt2/unflatten/FileExportHeaderReader.h b/tools/aapt2/unflatten/FileExportHeaderReader.h
deleted file mode 100644
index e552ea1..0000000
--- a/tools/aapt2/unflatten/FileExportHeaderReader.h
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H
-#define AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H
-
-#include "ResChunkPullParser.h"
-#include "Resource.h"
-#include "ResourceUtils.h"
-
-#include "flatten/ResourceTypeExtensions.h"
-#include "util/StringPiece.h"
-#include "util/Util.h"
-
-#include <androidfw/ResourceTypes.h>
-#include <sstream>
-#include <string>
-
-namespace aapt {
-
-static ssize_t parseFileExportHeaderImpl(const void* data, const size_t len,
-                                         const FileExport_header** outFileExport,
-                                         const ExportedSymbol** outExportedSymbolIndices,
-                                         android::ResStringPool* outStringPool,
-                                         std::string* outError) {
-    ResChunkPullParser parser(data, len);
-    if (!ResChunkPullParser::isGoodEvent(parser.next())) {
-        if (outError) *outError = parser.getLastError();
-        return -1;
-    }
-
-    if (util::deviceToHost16(parser.getChunk()->type) != RES_FILE_EXPORT_TYPE) {
-        if (outError) *outError = "no FileExport_header found";
-        return -1;
-    }
-
-    const FileExport_header* fileExport = convertTo<FileExport_header>(parser.getChunk());
-    if (!fileExport) {
-        if (outError) *outError = "corrupt FileExport_header";
-        return -1;
-    }
-
-    if (memcmp(fileExport->magic, "AAPT", sizeof(fileExport->magic)) != 0) {
-        if (outError) *outError = "invalid magic value";
-        return -1;
-    }
-
-    const size_t exportedSymbolCount = util::deviceToHost32(fileExport->exportedSymbolCount);
-
-    // Verify that we have enough space for all those symbols.
-    size_t dataLen = getChunkDataLen(&fileExport->header);
-    if (exportedSymbolCount > dataLen / sizeof(ExportedSymbol)) {
-        if (outError) *outError = "too many symbols";
-        return -1;
-    }
-
-    const size_t symbolIndicesSize = exportedSymbolCount * sizeof(ExportedSymbol);
-
-    const void* strPoolData = getChunkData(&fileExport->header) + symbolIndicesSize;
-    const size_t strPoolDataLen = dataLen - symbolIndicesSize;
-    if (outStringPool->setTo(strPoolData, strPoolDataLen, false) != android::NO_ERROR) {
-        if (outError) *outError = "corrupt string pool";
-        return -1;
-    }
-
-    *outFileExport = fileExport;
-    *outExportedSymbolIndices = (const ExportedSymbol*) getChunkData(
-            &fileExport->header);
-    return util::deviceToHost16(fileExport->header.headerSize) + symbolIndicesSize +
-            outStringPool->bytes();
-}
-
-static ssize_t getWrappedDataOffset(const void* data, size_t len, std::string* outError) {
-    const FileExport_header* header = nullptr;
-    const ExportedSymbol* entries = nullptr;
-    android::ResStringPool pool;
-    return parseFileExportHeaderImpl(data, len, &header, &entries, &pool, outError);
-}
-
-/**
- * Reads the FileExport_header and populates outRes with the values in that header.
- */
-static ssize_t unwrapFileExportHeader(const void* data, size_t len, ResourceFile* outRes,
-                                      std::string* outError) {
-
-    const FileExport_header* fileExport = nullptr;
-    const ExportedSymbol* entries = nullptr;
-    android::ResStringPool symbolPool;
-    const ssize_t offset = parseFileExportHeaderImpl(data, len, &fileExport, &entries, &symbolPool,
-                                                     outError);
-    if (offset < 0) {
-        return offset;
-    }
-
-    const size_t exportedSymbolCount = util::deviceToHost32(fileExport->exportedSymbolCount);
-    outRes->exportedSymbols.clear();
-    outRes->exportedSymbols.reserve(exportedSymbolCount);
-
-    for (size_t i = 0; i < exportedSymbolCount; i++) {
-        const StringPiece16 str = util::getString(symbolPool,
-                                                  util::deviceToHost32(entries[i].name.index));
-        StringPiece16 packageStr, typeStr, entryStr;
-        ResourceUtils::extractResourceName(str, &packageStr, &typeStr, &entryStr);
-        const ResourceType* resType = parseResourceType(typeStr);
-        if (!resType || entryStr.empty()) {
-            if (outError) {
-                std::stringstream errorStr;
-                errorStr << "invalid exported symbol at index="
-                         << util::deviceToHost32(entries[i].name.index)
-                         << " (" << str << ")";
-                *outError = errorStr.str();
-            }
-            return -1;
-        }
-
-        outRes->exportedSymbols.push_back(SourcedResourceName{
-                ResourceName{ packageStr.toString(), *resType, entryStr.toString() },
-                util::deviceToHost32(entries[i].line) });
-    }
-
-    const StringPiece16 str = util::getString(symbolPool,
-                                              util::deviceToHost32(fileExport->name.index));
-    StringPiece16 packageStr, typeStr, entryStr;
-    ResourceUtils::extractResourceName(str, &packageStr, &typeStr, &entryStr);
-    const ResourceType* resType = parseResourceType(typeStr);
-    if (!resType || entryStr.empty()) {
-        if (outError) {
-            std::stringstream errorStr;
-            errorStr << "invalid resource name at index="
-                     << util::deviceToHost32(fileExport->name.index)
-                     << " (" << str << ")";
-            *outError = errorStr.str();
-        }
-        return -1;
-    }
-
-    outRes->name = ResourceName{ packageStr.toString(), *resType, entryStr.toString() };
-    outRes->source.path = util::utf16ToUtf8(
-            util::getString(symbolPool, util::deviceToHost32(fileExport->source.index)));
-    outRes->config.copyFromDtoH(fileExport->config);
-    return offset;
-}
-
-} // namespace aapt
-
-#endif /* AAPT_UNFLATTEN_FILEEXPORTHEADERREADER_H */
diff --git a/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp b/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp
deleted file mode 100644
index a76c83b..0000000
--- a/tools/aapt2/unflatten/FileExportHeaderReader_test.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "Resource.h"
-
-#include "flatten/FileExportWriter.h"
-#include "unflatten/FileExportHeaderReader.h"
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-
-#include "test/Common.h"
-
-#include <gtest/gtest.h>
-
-namespace aapt {
-
-TEST(FileExportHeaderReaderTest, ReadHeaderWithNoSymbolExports) {
-    ResourceFile resFile = {
-            test::parseNameOrDie(u"@android:layout/main.xml"),
-            test::parseConfigOrDie("sw600dp-v4"),
-            Source{ "res/layout/main.xml" },
-    };
-
-    BigBuffer buffer(1024);
-    ChunkWriter writer = wrapBufferWithFileExportHeader(&buffer, &resFile);
-    *writer.getBuffer()->nextBlock<uint32_t>() = 42u;
-    writer.finish();
-
-    std::unique_ptr<uint8_t[]> data = util::copy(buffer);
-
-    ResourceFile actualResFile;
-
-    ssize_t offset = unwrapFileExportHeader(data.get(), buffer.size(), &actualResFile, nullptr);
-    ASSERT_GT(offset, 0);
-
-    EXPECT_EQ(offset, getWrappedDataOffset(data.get(), buffer.size(), nullptr));
-
-    EXPECT_EQ(actualResFile.config, test::parseConfigOrDie("sw600dp-v4"));
-    EXPECT_EQ(actualResFile.name, test::parseNameOrDie(u"@android:layout/main.xml"));
-    EXPECT_EQ(actualResFile.source.path, "res/layout/main.xml");
-
-    EXPECT_EQ(*(uint32_t*)(data.get() + offset), 42u);
-}
-
-} // namespace aapt
diff --git a/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
index 611ed15..9fc1706 100644
--- a/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
+++ b/wifi/java/android/net/wifi/PasspointManagementObjectDefinition.java
@@ -36,15 +36,15 @@
         mMoTree = moTree;
     }
 
-    public String getmBaseUri() {
+    public String getBaseUri() {
         return mBaseUri;
     }
 
-    public String getmUrn() {
+    public String getUrn() {
         return mUrn;
     }
 
-    public String getmMoTree() {
+    public String getMoTree() {
         return mMoTree;
     }
 
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 4a86c59..31da670 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -332,6 +332,7 @@
     public static class InformationElement {
         public static final int EID_SSID = 0;
         public static final int EID_BSS_LOAD = 11;
+        public static final int EID_RSN = 48;
         public static final int EID_HT_OPERATION = 61;
         public static final int EID_INTERWORKING = 107;
         public static final int EID_ROAMING_CONSORTIUM = 111;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index a0dbd85..362738e 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -101,6 +101,8 @@
     /** @hide */
     public static final String CA_CERT_KEY         = "ca_cert";
     /** @hide */
+    public static final String CA_PATH_KEY         = "ca_path";
+    /** @hide */
     public static final String ENGINE_KEY          = "engine";
     /** @hide */
     public static final String ENGINE_ID_KEY       = "engine_id";
@@ -625,6 +627,33 @@
         mCaCerts = null;
     }
 
+    /**
+     * Set the ca_path directive on wpa_supplicant.
+     *
+     * From wpa_supplicant documentation:
+     *
+     * Directory path for CA certificate files (PEM). This path may contain
+     * multiple CA certificates in OpenSSL format. Common use for this is to
+     * point to system trusted CA list which is often installed into directory
+     * like /etc/ssl/certs. If configured, these certificates are added to the
+     * list of trusted CAs. ca_cert may also be included in that case, but it is
+     * not required.
+     * @param domain The path for CA certificate files
+     * @hide
+     */
+    public void setCaPath(String path) {
+        setFieldValue(CA_PATH_KEY, path);
+    }
+
+    /**
+     * Get the domain_suffix_match value. See setDomSuffixMatch.
+     * @return The path for CA certificate files.
+     * @hide
+     */
+    public String getCaPath() {
+        return getFieldValue(CA_PATH_KEY, "");
+    }
+
     /** Set Client certificate alias.
      *
      * <p> See the {@link android.security.KeyChain} for details on installing or choosing