Merge "Allow apps to know what users think of their notifications." into nyc-dev
diff --git a/api/current.txt b/api/current.txt
index 033f924..1d891a6 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
@@ -34047,6 +34047,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();
@@ -34071,6 +34072,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);
@@ -40663,6 +40665,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();
@@ -43166,6 +43183,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);
@@ -43217,6 +43235,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();
@@ -43342,6 +43361,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);
   }
diff --git a/api/system-current.txt b/api/system-current.txt
index cc74f67..1ab5837 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
@@ -36530,6 +36530,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();
@@ -36554,6 +36555,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);
@@ -43415,6 +43417,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();
@@ -45918,6 +45935,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);
@@ -45969,6 +45987,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();
@@ -46095,6 +46114,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);
   }
diff --git a/api/test-current.txt b/api/test-current.txt
index 1a2703c..49b8ffe 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
@@ -34062,6 +34062,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();
@@ -34086,6 +34087,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);
@@ -40680,6 +40682,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();
@@ -43183,6 +43200,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);
@@ -43234,6 +43252,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();
@@ -43359,6 +43378,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);
   }
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/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index c071162..df4b7d1 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -429,6 +429,16 @@
         }
     }
 
+    /** @hide */
+    @Override
+    public @Nullable String getServicesSystemSharedLibraryPackageName() {
+        try {
+            return mPM.getServicesSystemSharedLibraryPackageName();
+        } catch (RemoteException e) {
+            throw new RuntimeException("Package manager has died", e);
+        }
+    }
+
     @Override
     public FeatureInfo[] getSystemAvailableFeatures() {
         try {
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/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/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 593fe2a..3863857 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -534,4 +534,6 @@
     boolean isEphemeralApplication(String packageName, int userId);
 
     boolean setRequiredForSystemUser(String packageName, boolean systemUserApp);
+
+    String getServicesSystemSharedLibraryPackageName();
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 08deedb..bf0d4de 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2327,6 +2327,28 @@
     public static final int MASK_PERMISSION_FLAGS = 0xFF;
 
     /**
+     * This is a library that contains components apps can invoke. For
+     * example, a services for apps to bind to, or standard chooser UI,
+     * etc. This library is versioned and backwards compatible. Clients
+     * should check its version via {@link android.ext.services.Version
+     * #getVersionCode()} and avoid calling APIs added in later versions.
+     *
+     * @hide
+     */
+    public static final String SYSTEM_SHARED_LIBRARY_SERVICES = "android.ext.services";
+
+    /**
+     * This is a library that contains components apps can dynamically
+     * load. For example, new widgets, helper classes, etc. This library
+     * is versioned and backwards compatible. Clients should check its
+     * version via {@link android.ext.shared.Version#getVersionCode()}
+     * and avoid calling APIs added in later versions.
+     *
+     * @hide
+     */
+    public static final String SYSTEM_SHARED_LIBRARY_SHARED = "android.ext.shared";
+
+    /**
      * Retrieve overall information about an application package that is
      * installed on the system.
      *
@@ -3365,6 +3387,15 @@
     public abstract String[] getSystemSharedLibraryNames();
 
     /**
+     * Get the name of the package hosting the services shared library.
+     *
+     * @return The library host package.
+     *
+     * @hide
+     */
+    public abstract @Nullable String getServicesSystemSharedLibraryPackageName();
+
+    /**
      * Get a list of features that are available on the
      * system.
      *
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/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index daf2581..bffbc75 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -641,16 +641,22 @@
 
     @Override
     public void setTintList(ColorStateList tint) {
-        mBitmapState.mTint = tint;
-        mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
-        invalidateSelf();
+        final BitmapState state = mBitmapState;
+        if (state.mTint != tint) {
+            state.mTint = tint;
+            mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
+            invalidateSelf();
+        }
     }
 
     @Override
     public void setTintMode(PorterDuff.Mode tintMode) {
-        mBitmapState.mTintMode = tintMode;
-        mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode);
-        invalidateSelf();
+        final BitmapState state = mBitmapState;
+        if (state.mTintMode != tintMode) {
+            state.mTintMode = tintMode;
+            mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode);
+            invalidateSelf();
+        }
     }
 
     /**
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/TvContract.java b/media/java/android/media/tv/TvContract.java
index 62a01dc..82c5a8e 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -27,6 +27,7 @@
 import android.provider.BaseColumns;
 import android.util.ArraySet;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -1020,22 +1021,28 @@
          *
          * <p>Use the same language appeared in the underlying broadcast standard, if applicable.
          * (For example, one can refer to the genre strings used in Genre Descriptor of ATSC A/65 or
-         * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, leave empty.
+         * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, leave empty. Use
+         * {@link Genres#encode} to create a text that can be stored in this column. Use
+         * {@link Genres#decode} to get the broadcast genre strings from the text stored in the
+         * column.
          *
          * <p>Type: TEXT
+         * @see Genres#encode
+         * @see Genres#decode
          */
         public static final String COLUMN_BROADCAST_GENRE = "broadcast_genre";
 
         /**
          * The comma-separated canonical genre string of this TV program.
          *
-         * <p>Canonical genres are defined in {@link Genres}. Use
-         * {@link Genres#encode Genres.encode()} to create a text that can be stored in this column.
-         * Use {@link Genres#decode Genres.decode()} to get the canonical genre strings from the
-         * text stored in this column.
+         * <p>Canonical genres are defined in {@link Genres}. Use {@link Genres#encode} to create a
+         * text that can be stored in this column. Use {@link Genres#decode} to get the canonical
+         * genre strings from the text stored in the column.
          *
          * <p>Type: TEXT
          * @see Genres
+         * @see Genres#encode
+         * @see Genres#decode
          */
         public static final String COLUMN_CANONICAL_GENRE = "canonical_genre";
 
@@ -1303,34 +1310,87 @@
                 CANONICAL_GENRES.add(TECH_SCIENCE);
             }
 
+            private static final char DOUBLE_QUOTE = '"';
+            private static final char COMMA = ',';
+            private static final String DELIMITER = ",";
+
             private Genres() {}
 
             /**
-             * Encodes canonical genre strings to a text that can be put into the database.
+             * Encodes genre strings to a text that can be put into the database.
              *
-             * @param genres Canonical genre strings. Use the strings defined in this class.
+             * @param genres Genre strings.
              * @return an encoded genre string that can be inserted into the
-             *         {@link #COLUMN_CANONICAL_GENRE} column.
+             *         {@link #COLUMN_BROADCAST_GENRE} or {@link #COLUMN_CANONICAL_GENRE} column.
              */
             public static String encode(String... genres) {
                 StringBuilder sb = new StringBuilder();
                 String separator = "";
                 for (String genre : genres) {
-                    sb.append(separator).append(genre);
-                    separator = ",";
+                    sb.append(separator).append(encodeToCsv(genre));
+                    separator = DELIMITER;
+                }
+                return sb.toString();
+            }
+
+            private static String encodeToCsv(String genre) {
+                StringBuilder sb = new StringBuilder();
+                int length = genre.length();
+                for (int i = 0; i < length; ++i) {
+                    char c = genre.charAt(i);
+                    switch (c) {
+                        case DOUBLE_QUOTE:
+                            sb.append(DOUBLE_QUOTE);
+                            break;
+                        case COMMA:
+                            sb.append(DOUBLE_QUOTE);
+                            break;
+                    }
+                    sb.append(c);
                 }
                 return sb.toString();
             }
 
             /**
-             * Decodes the canonical genre strings from the text stored in the database.
+             * Decodes the genre strings from the text stored in the database.
              *
              * @param genres The encoded genre string retrieved from the
-             *            {@link #COLUMN_CANONICAL_GENRE} column.
-             * @return canonical genre strings.
+             *            {@link #COLUMN_BROADCAST_GENRE} or {@link #COLUMN_CANONICAL_GENRE} column.
+             * @return genre strings.
              */
             public static String[] decode(String genres) {
-                return genres.split("\\s*,\\s*");
+                StringBuilder sb = new StringBuilder();
+                List<String> results = new ArrayList<>();
+                int length = genres.length();
+                boolean escape = false;
+                for (int i = 0; i < length; ++i) {
+                    char c = genres.charAt(i);
+                    switch (c) {
+                        case DOUBLE_QUOTE:
+                            if (!escape) {
+                                escape = true;
+                                continue;
+                            }
+                            break;
+                        case COMMA:
+                            if (!escape) {
+                                String string = sb.toString().trim();
+                                if (string.length() > 0) {
+                                    results.add(string);
+                                }
+                                sb = new StringBuilder();
+                                continue;
+                            }
+                            break;
+                    }
+                    sb.append(c);
+                    escape = false;
+                }
+                String string = sb.toString().trim();
+                if (string.length() > 0) {
+                    results.add(string);
+                }
+                return results.toArray(new String[results.size()]);
             }
 
             /**
@@ -1441,7 +1501,10 @@
          *
          * <p>Use the same language appeared in the underlying broadcast standard, if applicable.
          * (For example, one can refer to the genre strings used in Genre Descriptor of ATSC A/65 or
-         * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, leave empty.
+         * Content Descriptor of ETSI EN 300 468, if appropriate.) Otherwise, leave empty. Use
+         * {@link Genres#encode Genres.encode()} to create a text that can be stored in this column.
+         * Use {@link Genres#decode Genres.decode()} to get the broadcast genre strings from the
+         * text stored in the column.
          *
          * <p>Type: TEXT
          * @see Programs#COLUMN_BROADCAST_GENRE
@@ -1454,7 +1517,7 @@
          * <p>Canonical genres are defined in {@link Programs.Genres}. Use
          * {@link Programs.Genres#encode Genres.encode()} to create a text that can be stored in
          * this column. Use {@link Programs.Genres#decode Genres.decode()} to get the canonical
-         * genre strings from the text stored in this column.
+         * genre strings from the text stored in the column.
          *
          * <p>Type: TEXT
          * @see Programs#COLUMN_CANONICAL_GENRE
diff --git a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
index 3c21a21..475387b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/BaseActivity.java
@@ -216,11 +216,13 @@
         return state;
     }
 
-    void onStackRestored(boolean restored, boolean external) {}
+    public void setRootsDrawerOpen(boolean open) {
+        mNavigator.revealRootsDrawer(open);
+    }
 
     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;
         }
 
@@ -539,7 +541,8 @@
         // Do some "do what a I want" drawer fiddling, but don't
         // do it if user already hit back recently and we recently
         // did some fiddling.
-        if ((System.currentTimeMillis() - mDrawerLastFiddled) > DRAWER_NO_FIDDLE_DELAY) {
+        if (mDrawer.isPresent()
+                && (System.currentTimeMillis() - mDrawerLastFiddled) > DRAWER_NO_FIDDLE_DELAY) {
             // Close drawer if it is open.
             if (mDrawer.isOpen()) {
                 mDrawer.setOpen(false);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 3485fe4..80bdfda 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -32,7 +32,6 @@
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
-import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
@@ -141,8 +140,7 @@
         }
     }
 
-    @Override
-    void onStackRestored(boolean restored, boolean external) {
+    private void onStackRestored(boolean restored, boolean external) {
         // Show drawer when no stack restored, but only when requesting
         // non-visual content. However, if we last used an external app,
         // drawer is always shown.
@@ -329,10 +327,6 @@
         mNavigator.revealRootsDrawer(false);
     }
 
-    public void setRootsDrawerOpen(boolean open) {
-        mNavigator.revealRootsDrawer(open);
-    }
-
     @Override
     public void onDocumentPicked(DocumentInfo doc, SiblingProvider siblings) {
         final FragmentManager fm = getFragmentManager();
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/State.java b/packages/DocumentsUI/src/com/android/documentsui/State.java
index d141de6..139fb45 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/State.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/State.java
@@ -16,12 +16,16 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.Shared.DEBUG;
+
 import android.annotation.IntDef;
 import android.content.Intent;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.model.DocumentInfo;
 import com.android.documentsui.model.DocumentStack;
 import com.android.documentsui.model.DurableUtils;
@@ -35,6 +39,8 @@
 
 public class State implements android.os.Parcelable {
 
+    private static final String TAG = "State";
+
     public static final int ACTION_OPEN = 1;
     public static final int ACTION_CREATE = 2;
     public static final int ACTION_GET_CONTENT = 3;
@@ -85,6 +91,8 @@
     /** Current user navigation stack; empty implies recents. */
     public DocumentStack stack = new DocumentStack();
     private boolean mStackTouched;
+    private boolean mInitialRootChanged;
+    private boolean mInitialDocChanged;
 
     /** Currently active search, overriding any stack. */
     public String currentSearch;
@@ -92,8 +100,11 @@
     /** Instance state for every shown directory */
     public HashMap<String, SparseArray<Parcelable>> dirState = new HashMap<>();
 
+    /** UI selection */
+    public Selection selectedDocuments = new Selection();
+
     /** Currently copying file */
-    public List<DocumentInfo> selectedDocumentsForCopy = new ArrayList<DocumentInfo>();
+    public List<DocumentInfo> selectedDocumentsForCopy = new ArrayList<>();
 
     /** Name of the package that started DocsUI */
     public List<String> excludedAuthorities = new ArrayList<>();
@@ -108,22 +119,32 @@
     }
 
     public void onRootChanged(RootInfo root) {
+        if (DEBUG) Log.d(TAG, "Root changed to: " + root);
+        if (!mInitialRootChanged && stack.root != null && !root.equals(stack.root)) {
+            mInitialRootChanged = true;
+        }
         stack.root = root;
         stack.clear();
         mStackTouched = true;
     }
 
     public void pushDocument(DocumentInfo info) {
+        if (DEBUG) Log.d(TAG, "Adding doc to stack: " + info);
+        if (!mInitialDocChanged && stack.size() > 0 && !info.equals(stack.peek())) {
+            mInitialDocChanged = true;
+        }
         stack.push(info);
         mStackTouched = true;
     }
 
     public void popDocument() {
+        if (DEBUG) Log.d(TAG, "Popping doc off stack.");
         stack.pop();
         mStackTouched = true;
     }
 
     public void setStack(DocumentStack stack) {
+        if (DEBUG) Log.d(TAG, "Setting the whole darn stack to: " + stack);
         this.stack = stack;
         mStackTouched = true;
     }
@@ -132,6 +153,10 @@
         return mStackTouched;
     }
 
+    public boolean initialiLocationHasChanged() {
+        return mInitialRootChanged || mInitialDocChanged;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -152,10 +177,13 @@
         DurableUtils.writeToParcel(out, stack);
         out.writeString(currentSearch);
         out.writeMap(dirState);
+        out.writeParcelable(selectedDocuments, 0);
         out.writeList(selectedDocumentsForCopy);
         out.writeList(excludedAuthorities);
         out.writeInt(openableOnly ? 1 : 0);
         out.writeInt(mStackTouched ? 1 : 0);
+        out.writeInt(mInitialRootChanged ? 1 : 0);
+        out.writeInt(mInitialDocChanged ? 1 : 0);
     }
 
     public static final ClassLoaderCreator<State> CREATOR = new ClassLoaderCreator<State>() {
@@ -180,10 +208,13 @@
             DurableUtils.readFromParcel(in, state.stack);
             state.currentSearch = in.readString();
             in.readMap(state.dirState, loader);
+            state.selectedDocuments = in.readParcelable(loader);
             in.readList(state.selectedDocumentsForCopy, loader);
             in.readList(state.excludedAuthorities, loader);
             state.openableOnly = in.readInt() != 0;
             state.mStackTouched = in.readInt() != 0;
+            state.mInitialRootChanged = in.readInt() != 0;
+            state.mInitialDocChanged = in.readInt() != 0;
             return state;
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index d21b157..7fe881e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -27,6 +27,7 @@
 import static com.android.internal.util.Preconditions.checkState;
 import static com.google.common.base.Preconditions.checkArgument;
 
+import android.annotation.IntDef;
 import android.annotation.StringRes;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -53,9 +54,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,14 +93,19 @@
 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;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -111,6 +115,13 @@
  */
 public class DirectoryFragment extends Fragment implements DocumentsAdapter.Environment {
 
+    @IntDef(flag = true, value = {
+            TYPE_NORMAL,
+            TYPE_SEARCH,
+            TYPE_RECENT_OPEN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResultType {}
     public static final int TYPE_NORMAL = 1;
     public static final int TYPE_SEARCH = 2;
     public static final int TYPE_RECENT_OPEN = 3;
@@ -147,7 +158,7 @@
     private RecyclerView mRecView;
     private ListeningGestureDetector mGestureDetector;
 
-    private int mType = TYPE_NORMAL;
+    private @ResultType int mType = TYPE_NORMAL;
     private String mStateKey;
 
     private int mLastSortOrder = SORT_ORDER_UNKNOWN;
@@ -155,9 +166,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 +191,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).
@@ -218,9 +211,6 @@
             final View view = mRecView.getChildAt(i);
             cancelThumbnailTask(view);
         }
-
-        // Clear any outstanding selection
-        mSelectionManager.clearSelection();
     }
 
     @Override
@@ -232,6 +222,7 @@
 
         final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
         final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
+        mStateKey = buildStateKey(root, doc);
 
         mIconHelper = new IconHelper(context, MODE_GRID);
 
@@ -240,10 +231,24 @@
 
         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);
 
+        // final here because we'll manually bump the listener iwhen we had an initial selection,
+        // but only after the model is fully loaded.
+        final SelectionModeListener selectionListener = new SelectionModeListener();
+        final Selection initialSelection = state.selectedDocuments.hasDirectoryKey(mStateKey)
+            ? state.selectedDocuments
+            : null;
+
         // TODO: instead of inserting the view into the constructor, extract listener-creation code
         // and set the listener on the view after the fact.  Then the view doesn't need to be passed
         // into the selection manager.
@@ -252,17 +257,18 @@
                 mAdapter,
                 state.allowMultiple
                     ? MultiSelectManager.MODE_MULTIPLE
-                    : MultiSelectManager.MODE_SINGLE);
-        mSelectionManager.addCallback(new SelectionModeListener());
+                    : MultiSelectManager.MODE_SINGLE,
+                initialSelection);
+
+        mSelectionManager.addCallback(selectionListener);
 
         mModel = new Model();
         mModel.addUpdateListener(mAdapter);
         mModel.addUpdateListener(mModelUpdateListener);
 
         mType = getArguments().getInt(EXTRA_TYPE);
-        mStateKey = buildStateKey(root, doc);
 
-        mTuner = FragmentTuner.pick(state);
+        mTuner = FragmentTuner.pick(getContext(), state);
         mClipper = new DocumentClipper(context);
 
         boolean hideGridTitles;
@@ -320,10 +326,8 @@
 
                 updateDisplayState();
 
-                // When launched into empty recents, show drawer
-                if (mType == TYPE_RECENT_OPEN && mModel.isEmpty() && !state.hasLocationChanged() &&
-                        context instanceof DocumentsActivity) {
-                    ((DocumentsActivity) context).setRootsDrawerOpen(true);
+                if (initialSelection != null) {
+                    selectionListener.onSelectionChanged();
                 }
 
                 // Restore any previous instance state
@@ -338,6 +342,8 @@
                 }
 
                 mLastSortOrder = state.derivedSortOrder;
+
+                mTuner.onModelLoaded(mModel, mType);
             }
 
             @Override
@@ -351,6 +357,18 @@
     }
 
     @Override
+    public void onSaveInstanceState(Bundle outState) {
+        State state = getDisplayState();
+        if (mSelectionManager.hasSelection()) {
+            mSelectionManager.getSelection(state.selectedDocuments);
+            state.selectedDocuments.setDirectoryKey(mStateKey);
+            if (!state.selectedDocuments.isEmpty()) {
+                if (DEBUG) Log.d(TAG, "Persisted selection: " + state.selectedDocuments);
+            }
+        }
+    }
+
+    @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         // There's only one request code right now. Replace this with a switch statement or
         // something more scalable when more codes are added.
@@ -432,41 +450,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 +483,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 +787,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(
@@ -1280,12 +1283,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.
@@ -1321,6 +1326,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/src/com/android/documentsui/dirlist/FragmentTuner.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
index a295ab2..3f51e538 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/FragmentTuner.java
@@ -16,6 +16,7 @@
 
 package com.android.documentsui.dirlist;
 
+import static com.android.documentsui.Shared.DEBUG;
 import static com.android.documentsui.State.ACTION_BROWSE;
 import static com.android.documentsui.State.ACTION_CREATE;
 import static com.android.documentsui.State.ACTION_GET_CONTENT;
@@ -24,15 +25,20 @@
 import static com.android.documentsui.State.ACTION_OPEN_TREE;
 import static com.android.internal.util.Preconditions.checkArgument;
 
+import android.content.Context;
+import android.os.SystemProperties;
+import android.provider.DocumentsContract.Document;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.android.documentsui.DocumentsActivity;
+import com.android.documentsui.FilesActivity;
 import com.android.documentsui.Menus;
 import com.android.documentsui.MimePredicate;
 import com.android.documentsui.R;
 import com.android.documentsui.State;
-
-import android.os.SystemProperties;
-import android.provider.DocumentsContract.Document;
-import android.view.Menu;
-import android.view.MenuItem;
+import com.android.documentsui.dirlist.DirectoryFragment.ResultType;
 
 /**
  * Providers support for specializing the DirectoryFragment to the "host" Activity.
@@ -40,20 +46,22 @@
  */
 public abstract class FragmentTuner {
 
+    final Context mContext;
     final State mState;
 
-    public FragmentTuner(State state) {
+    public FragmentTuner(Context context, State state) {
+        mContext = context;
         mState = state;
     }
 
-    public static FragmentTuner pick(State state) {
+    public static FragmentTuner pick(Context context, State state) {
         switch (state.action) {
             case ACTION_BROWSE:
-                return new FilesTuner(state);
+                return new FilesTuner(context, state);
             case ACTION_MANAGE:
-                return new DownloadsTuner(state);
+                return new DownloadsTuner(context, state);
             default:
-                return new DocumentsTuner(state);
+                return new DocumentsTuner(context, state);
         }
     }
 
@@ -76,13 +84,15 @@
         return MimePredicate.mimeMatches(mState.acceptMimes, docMimeType);
     }
 
+    abstract void onModelLoaded(Model model, @ResultType int resultType);
+
     /**
      * Provides support for Platform specific specializations of DirectoryFragment.
      */
     private static final class DocumentsTuner extends FragmentTuner {
 
-        public DocumentsTuner(State state) {
-            super(state);
+        public DocumentsTuner(Context context, State state) {
+            super(context, state);
         }
 
         @Override
@@ -154,6 +164,16 @@
             moveTo.setEnabled(moveEnabled);
             rename.setVisible(false);
         }
+
+        @Override
+        void onModelLoaded(Model model, @ResultType int resultType) {
+            // When launched into empty recents, show drawer
+            if (resultType == DirectoryFragment.TYPE_RECENT_OPEN
+                    && model.isEmpty()
+                    && !mState.hasLocationChanged()) {
+                ((DocumentsActivity) mContext).setRootsDrawerOpen(true);
+            }
+        }
     }
 
     /**
@@ -161,8 +181,8 @@
      */
     private static final class DownloadsTuner extends FragmentTuner {
 
-        public DownloadsTuner(State state) {
-            super(state);
+        public DownloadsTuner(Context context, State state) {
+            super(context, state);
         }
 
         @Override
@@ -189,6 +209,9 @@
             moveTo.setEnabled(moveEnabled);
             rename.setVisible(false);
         }
+
+        @Override
+        void onModelLoaded(Model model, @ResultType int resultType) {}
     }
 
     /**
@@ -196,8 +219,10 @@
      */
     private static final class FilesTuner extends FragmentTuner {
 
-        public FilesTuner(State state) {
-            super(state);
+        private static final String TAG = "FilesTuner";
+
+        public FilesTuner(Context context, State state) {
+            super(context, state);
         }
 
         @Override
@@ -221,9 +246,23 @@
 
             Menus.disableHiddenItems(menu, copy, paste);
         }
+
+        @Override
+        void onModelLoaded(Model model, @ResultType int resultType) {
+            if (DEBUG) Log.d(TAG, "Handling model loaded. Has Location shcnage: " + mState.initialiLocationHasChanged());
+            // When launched into empty root, open drawer.
+            if (model.isEmpty() && !mState.initialiLocationHasChanged()) {
+                if (DEBUG) Log.d(TAG, "Showing roots drawer cuz stuffs empty.");
+
+                // This noops on layouts without drawer, so no need to guard.
+                ((FilesActivity) mContext).setRootsDrawerOpen(true);
+            }
+            if (DEBUG) Log.d(TAG, "Donezo.");
+        }
     }
 
     private static boolean isDirectory(String mimeType) {
         return Document.MIME_TYPE_DIR.equals(mimeType);
     }
+
 }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 516b25e..d60825b 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -23,9 +23,12 @@
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.internal.util.Preconditions.checkState;
 
+import android.annotation.IntDef;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import android.support.v7.widget.GridLayoutManager;
@@ -41,12 +44,13 @@
 import com.android.documentsui.Events.MotionInputEvent;
 import com.android.documentsui.R;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 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.Map;
 import java.util.Set;
@@ -58,10 +62,13 @@
  */
 public final class MultiSelectManager {
 
-    /** Selection mode for multiple select. **/
+    @IntDef(flag = true, value = {
+            MODE_MULTIPLE,
+            MODE_SINGLE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SelectionMode {}
     public static final int MODE_MULTIPLE = 0;
-
-    /** Selection mode for multiple select. **/
     public static final int MODE_SINGLE = 1;
 
     private static final String TAG = "MultiSelectManager";
@@ -79,14 +86,19 @@
 
 
     /**
-     * @param recyclerView
-     * @param mode Selection mode
+     * @param mode Selection single or multiple selection mode.
+     * @param initialSelection selection state probably preserved in external state.
      */
     public MultiSelectManager(
-            final RecyclerView recyclerView, DocumentsAdapter adapter, int mode) {
-        this(new RuntimeSelectionEnvironment(recyclerView), adapter, mode);
+            final RecyclerView recyclerView,
+            DocumentsAdapter adapter,
+            @SelectionMode int mode,
+            @Nullable Selection initialSelection) {
+
+        this(new RuntimeSelectionEnvironment(recyclerView), adapter, mode, initialSelection);
 
         if (mode == MODE_MULTIPLE) {
+            // TODO: Don't load this on low memory devices.
             mBandManager = new BandController();
         }
 
@@ -116,10 +128,18 @@
      * @hide
      */
     @VisibleForTesting
-    MultiSelectManager(SelectionEnvironment environment, DocumentsAdapter adapter, int mode) {
+    MultiSelectManager(
+            SelectionEnvironment environment,
+            DocumentsAdapter adapter,
+            @SelectionMode int mode,
+            @Nullable Selection initialSelection) {
+
         mEnvironment = checkNotNull(environment, "'environment' cannot be null.");
         mAdapter = checkNotNull(adapter, "'adapter' cannot be null.");
         mSingleSelect = mode == MODE_SINGLE;
+        if (initialSelection != null) {
+            mSelection.copyFrom(initialSelection);
+        }
 
         mAdapter.registerAdapterDataObserver(
                 new RecyclerView.AdapterDataObserver() {
@@ -203,6 +223,13 @@
     }
 
     /**
+     * Updates selection to include items in {@code selection}.
+     */
+    public void updateSelection(Selection selection) {
+        setItemsSelected(selection.toList(), true);
+    }
+
+    /**
      * Sets the selected state of the specified items. Note that the callback will NOT
      * be consulted to see if an item can be selected.
      *
@@ -615,7 +642,7 @@
      * Object representing the current selection. Provides read only access
      * public access, and private write access.
      */
-    public static final class Selection {
+    public static final class Selection implements Parcelable {
 
         // This class tracks selected items by managing two sets: the saved selection, and the total
         // selection. Saved selections are those which have been completed by tapping an item or by
@@ -628,8 +655,9 @@
         // item A is tapped (and selected), then an in-progress band select covers A then uncovers
         // A, A should still be selected as it has been saved. To ensure this behavior, the saved
         // selection must be tracked separately.
-        private Set<String> mSavedSelection = new HashSet<>();
-        private Set<String> mTotalSelection = new HashSet<>();
+        private Set<String> mSelection = new HashSet<>();
+        private Set<String> mProvisionalSelection = new HashSet<>();
+        private String mDirectoryKey;
 
         @VisibleForTesting
         public Selection(String... ids) {
@@ -643,53 +671,70 @@
          * @return true if the position is currently selected.
          */
         public boolean contains(@Nullable String id) {
-            return mTotalSelection.contains(id);
+            return mSelection.contains(id) || mProvisionalSelection.contains(id);
         }
 
         /**
          * Returns an unordered array of selected positions.
          */
         public String[] getAll() {
-            return mTotalSelection.toArray(new String[0]);
+            return toList().toArray(new String[0]);
+        }
+
+        /**
+         * Returns an unordered array of selected positions (including any
+         * provisional selections current in effect).
+         */
+        private List<String> toList() {
+            ArrayList<String> selection = new ArrayList<String>(mSelection);
+            selection.addAll(mProvisionalSelection);
+            return selection;
         }
 
         /**
          * @return size of the selection.
          */
         public int size() {
-            return mTotalSelection.size();
+            return mSelection.size() + mProvisionalSelection.size();
         }
 
         /**
          * @return true if the selection is empty.
          */
         public boolean isEmpty() {
-            return mTotalSelection.isEmpty();
+            return mSelection.isEmpty() && mProvisionalSelection.isEmpty();
         }
 
         /**
          * Sets the provisional selection, which is a temporary selection that can be saved,
          * canceled, or adjusted at a later time. When a new provision selection is applied, the old
          * one (if it exists) is abandoned.
-         * @return Array with entry for each position added or removed. Entries which were added
-         *     contain a value of true, and entries which were removed contain a value of false.
+         * @return Map of ids added or removed. Added ids have a value of true, removed are false.
          */
         @VisibleForTesting
-        protected Map<String, Boolean> setProvisionalSelection(Set<String> provisionalSelection) {
+        protected Map<String, Boolean> setProvisionalSelection(Set<String> newSelection) {
             Map<String, Boolean> delta = new HashMap<>();
 
-            for (String id: mTotalSelection) {
+            for (String id: mProvisionalSelection) {
                 // Mark each item that used to be in the selection but is unsaved and not in the new
                 // provisional selection.
-                if (!provisionalSelection.contains(id) && !mSavedSelection.contains(id)) {
+                if (!newSelection.contains(id) && !mSelection.contains(id)) {
                     delta.put(id, false);
                 }
             }
 
-            for (String id: provisionalSelection) {
+            for (String id: mSelection) {
+                // Mark each item that used to be in the selection but is unsaved and not in the new
+                // provisional selection.
+                if (!newSelection.contains(id)) {
+                    delta.put(id, false);
+                }
+            }
+
+            for (String id: newSelection) {
                 // Mark each item that was not previously in the selection but is in the new
                 // provisional selection.
-                if (!mTotalSelection.contains(id)) {
+                if (!mSelection.contains(id) && !mProvisionalSelection.contains(id)) {
                     delta.put(id, true);
                 }
             }
@@ -700,9 +745,9 @@
             for (Map.Entry<String, Boolean> entry: delta.entrySet()) {
                 String id = entry.getKey();
                 if (entry.getValue()) {
-                    mTotalSelection.add(id);
+                    mProvisionalSelection.add(id);
                 } else {
-                    mTotalSelection.remove(id);
+                    mProvisionalSelection.remove(id);
                 }
             }
 
@@ -716,7 +761,8 @@
          */
         @VisibleForTesting
         protected void applyProvisionalSelection() {
-            mSavedSelection = new HashSet<>(mTotalSelection);
+            mSelection.addAll(mProvisionalSelection);
+            mProvisionalSelection.clear();
         }
 
         /**
@@ -725,15 +771,14 @@
          */
         @VisibleForTesting
         void cancelProvisionalSelection() {
-            mTotalSelection = new HashSet<>(mSavedSelection);
+            mProvisionalSelection.clear();
         }
 
         /** @hide */
         @VisibleForTesting
         boolean add(String id) {
-            if (!mTotalSelection.contains(id)) {
-                mTotalSelection.add(id);
-                mSavedSelection.add(id);
+            if (!mSelection.contains(id)) {
+                mSelection.add(id);
                 return true;
             }
             return false;
@@ -742,31 +787,29 @@
         /** @hide */
         @VisibleForTesting
         boolean remove(String id) {
-            if (mTotalSelection.contains(id)) {
-                mTotalSelection.remove(id);
-                mSavedSelection.remove(id);
+            if (mSelection.contains(id)) {
+                mSelection.remove(id);
                 return true;
             }
             return false;
         }
 
         public void clear() {
-            mSavedSelection.clear();
-            mTotalSelection.clear();
+            mSelection.clear();
         }
 
         /**
          * Trims this selection to be the intersection of itself with the set of given IDs.
          */
         public void intersect(Collection<String> ids) {
-            mSavedSelection.retainAll(ids);
-            mTotalSelection.retainAll(ids);
+            mSelection.retainAll(ids);
+            mProvisionalSelection.retainAll(ids);
         }
 
         @VisibleForTesting
         void copyFrom(Selection source) {
-            mSavedSelection = new HashSet<>(source.mSavedSelection);
-            mTotalSelection = new HashSet<>(source.mTotalSelection);
+            mSelection = new HashSet<>(source.mSelection);
+            mProvisionalSelection = new HashSet<>(source.mProvisionalSelection);
         }
 
         @Override
@@ -775,24 +818,19 @@
                 return "size=0, items=[]";
             }
 
-            StringBuilder buffer = new StringBuilder(mTotalSelection.size() * 28);
-            buffer.append("{size=")
-                    .append(mTotalSelection.size())
-                    .append(", ")
-                    .append("items=[");
-            for (Iterator<String> i = mTotalSelection.iterator(); i.hasNext(); ) {
-                buffer.append(i.next());
-                if (i.hasNext()) {
-                    buffer.append(", ");
-                }
-            }
-            buffer.append("]}");
+            StringBuilder buffer = new StringBuilder(size() * 28);
+            buffer.append("Selection{")
+                .append("applied{size=" + mSelection.size())
+                .append(", entries=" + mSelection)
+                .append("}, provisional{size=" + mProvisionalSelection.size())
+                .append(", entries=" + mProvisionalSelection)
+                .append("}}");
             return buffer.toString();
         }
 
         @Override
         public int hashCode() {
-            return mSavedSelection.hashCode() ^ mTotalSelection.hashCode();
+            return mSelection.hashCode() ^ mProvisionalSelection.hashCode();
         }
 
         @Override
@@ -805,8 +843,38 @@
               return false;
           }
 
-          return mSavedSelection.equals(((Selection) that).mSavedSelection) &&
-                  mTotalSelection.equals(((Selection) that).mTotalSelection);
+          return mSelection.equals(((Selection) that).mSelection) &&
+                  mProvisionalSelection.equals(((Selection) that).mProvisionalSelection);
+        }
+
+        /**
+         * Sets the state key for this selection, which allows us to match selections
+         * to particular states (of DirectoryFragment). Basically this lets us avoid
+         * loading a persisted selection in the wrong directory.
+         */
+        public void setDirectoryKey(String key) {
+            mDirectoryKey = key;
+        }
+
+        /**
+         * Sets the state key for this selection, which allows us to match selections
+         * to particular states (of DirectoryFragment). Basically this lets us avoid
+         * loading a persisted selection in the wrong directory.
+         */
+        public boolean hasDirectoryKey(String key) {
+            return key.equals(mDirectoryKey);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(mDirectoryKey);
+            dest.writeList(new ArrayList<>(mSelection));
+            // We don't include provisional selection since it is
+            // typically coupled to some other runtime state (like a band).
         }
     }
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
index 737b376..c51f927 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
@@ -16,35 +16,18 @@
 
 package com.android.documentsui;
 
-import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
 import static com.android.documentsui.StubProvider.ROOT_0_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;
 import android.support.test.uiautomator.By;
-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;
-
-import com.android.documentsui.model.RootInfo;
 
 @LargeTest
 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";
-
     public DownloadsActivityUiTest() {
         super(DownloadsActivity.class);
     }
@@ -83,7 +66,7 @@
 
         mDocsHelper.createDocument(rootDir0, "yummers/sandwich", "Ham & Cheese.sandwich");
 
-        device.waitForIdle();
+        bot.waitForDocument("Ham & Cheese.sandwich");
         bot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
     }
 
@@ -108,4 +91,11 @@
         device.waitForIdle();
         assertNotNull(bot.menuShare());
     }
+
+    public void testClosesOnBack() throws Exception {
+        DownloadsActivity activity = getActivity();
+        device.pressBack();
+        device.wait(Until.gone(By.text(ROOT_0_ID)), TIMEOUT);  // wait for the window to go away
+        assertTrue(activity.isDestroyed());
+    }
 }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index 7fd2416..77f16d9 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -16,34 +16,15 @@
 
 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.support.test.uiautomator.By;
-import android.support.test.uiautomator.Configurator;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.Until;
 import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.documentsui.model.RootInfo;
 
 @LargeTest
 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";
-
     public FilesActivityUiTest() {
         super(FilesActivity.class);
     }
@@ -102,7 +83,7 @@
         bot.openRoot(ROOT_0_ID);
         mDocsHelper.createDocument(rootDir0, "yummers/sandwich", "Ham & Cheese.sandwich");
 
-        device.waitForIdle();
+        bot.waitForDocument("Ham & Cheese.sandwich");
         bot.assertHasDocuments("file0.log", "file1.png", "file2.csv", "Ham & Cheese.sandwich");
     }
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
index 303f2d1..1069a66 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
@@ -18,9 +18,6 @@
 
 import static com.android.documentsui.StubProvider.ROOT_0_ID;
 
-import android.support.test.uiautomator.UiObject;
-import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 
 @LargeTest
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 61da3df..b8d8795 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
@@ -19,8 +19,6 @@
 import static com.android.documentsui.StubProvider.ROOT_0_ID;
 import static com.android.documentsui.StubProvider.ROOT_1_ID;
 
-import android.support.test.uiautomator.UiObject;
-import android.test.InstrumentationTestCase;
 import android.test.suitebuilder.annotation.LargeTest;
 
 @LargeTest
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index 417fd24..4534c40 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -21,9 +21,11 @@
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.assertFalse;
 
+import android.app.Activity;
 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 +34,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;
@@ -192,6 +195,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();
     }
@@ -247,6 +258,10 @@
         mDevice.wait(Until.gone(SNACK_DELETE), mTimeout * 2);
     }
 
+    void waitForDocument(String label) throws UiObjectNotFoundException {
+        findDocument(label).waitForExists(mTimeout);
+    }
+
     void switchViewMode() {
         UiObject2 mode = menuGridMode();
         if (mode != null) {
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index b1cb29e..d95fb49 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -50,7 +50,7 @@
         mCallback = new TestCallback();
         mEnv = new TestSelectionEnvironment(items);
         mAdapter = new TestDocumentsAdapter(items);
-        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_MULTIPLE);
+        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_MULTIPLE, null);
         mManager.addCallback(mCallback);
     }
 
@@ -174,7 +174,7 @@
     }
 
     public void testSingleSelectMode() {
-        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE);
+        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null);
         mManager.addCallback(mCallback);
         longPress(20);
         tap(13);
@@ -182,7 +182,7 @@
     }
 
     public void testSingleSelectMode_ShiftTap() {
-        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE);
+        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null);
         mManager.addCallback(mCallback);
         longPress(13);
         shiftTap(20);
@@ -198,24 +198,73 @@
         provisional.append(2, true);
         s.setProvisionalSelection(getItemIds(provisional));
         assertSelection(items.get(1), items.get(2));
+    }
 
-        provisional.delete(1);
-        provisional.append(3, true);
+    public void testProvisionalSelection_Replace() {
+        Selection s = mManager.getSelection();
+
+        SparseBooleanArray provisional = new SparseBooleanArray();
+        provisional.append(1, true);
+        provisional.append(2, true);
         s.setProvisionalSelection(getItemIds(provisional));
-        assertSelection(items.get(2), items.get(3));
-
-        s.applyProvisionalSelection();
-        assertSelection(items.get(2), items.get(3));
 
         provisional.clear();
         provisional.append(3, true);
         provisional.append(4, true);
         s.setProvisionalSelection(getItemIds(provisional));
-        assertSelection(items.get(2), items.get(3), items.get(4));
+        assertSelection(items.get(3), items.get(4));
+    }
 
-        provisional.delete(3);
+    public void testProvisionalSelection_IntersectsExistingProvisionalSelection() {
+        Selection s = mManager.getSelection();
+
+        SparseBooleanArray provisional = new SparseBooleanArray();
+        provisional.append(1, true);
+        provisional.append(2, true);
         s.setProvisionalSelection(getItemIds(provisional));
-        assertSelection(items.get(2), items.get(3), items.get(4));
+
+        provisional.clear();
+        provisional.append(1, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+        assertSelection(items.get(1));
+    }
+
+    public void testProvisionalSelection_Apply() {
+        Selection s = mManager.getSelection();
+
+        SparseBooleanArray provisional = new SparseBooleanArray();
+        provisional.append(1, true);
+        provisional.append(2, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+        s.applyProvisionalSelection();
+        assertSelection(items.get(1), items.get(2));
+    }
+
+    public void testProvisionalSelection_Cancel() {
+        mManager.toggleSelection(items.get(1));
+        mManager.toggleSelection(items.get(2));
+        Selection s = mManager.getSelection();
+
+        SparseBooleanArray provisional = new SparseBooleanArray();
+        provisional.append(3, true);
+        provisional.append(4, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+        s.cancelProvisionalSelection();
+
+        // Original selection should remain.
+        assertSelection(items.get(1), items.get(2));
+    }
+
+    public void testProvisionalSelection_IntersectsAppliedSelection() {
+        mManager.toggleSelection(items.get(1));
+        mManager.toggleSelection(items.get(2));
+        Selection s = mManager.getSelection();
+
+        SparseBooleanArray provisional = new SparseBooleanArray();
+        provisional.append(2, true);
+        provisional.append(3, true);
+        s.setProvisionalSelection(getItemIds(provisional));
+        assertSelection(items.get(1), items.get(2), items.get(3));
     }
 
     private static Set<String> getItemIds(SparseBooleanArray selection) {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index ef1e8e2..90b5c09 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -18,7 +18,6 @@
 
 import android.content.ContentResolver;
 import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
 import android.mtp.MtpObjectInfo;
 import android.net.Uri;
 import android.os.Bundle;
@@ -262,31 +261,39 @@
             if (objectInfoList.length == 0 || getState() != STATE_LOADING) {
                 return;
             }
-            if (mNumLoaded == 0) {
-                mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId);
-            }
-            try {
+            try{
+                if (mNumLoaded == 0) {
+                    mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId);
+                }
                 mDatabase.getMapper().putChildDocuments(
                         mIdentifier.mDeviceId, mIdentifier.mDocumentId, objectInfoList);
                 mNumLoaded += objectInfoList.length;
-            } catch (SQLiteException exp) {
-                mError = exp;
-                mNumLoaded = 0;
-            }
-            if (getState() != STATE_LOADING) {
-                mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
+                if (getState() != STATE_LOADING) {
+                    mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
+                }
+            } catch (FileNotFoundException exception) {
+                setErrorInternal(exception);
             }
         }
 
-        void setError(Exception message) {
+        void setError(Exception error) {
             final int lastState = getState();
-            mError = message;
-            mNumLoaded = 0;
+            setErrorInternal(error);
             if (lastState == STATE_LOADING) {
-                mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
+                try {
+                    mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
+                } catch (FileNotFoundException exception) {
+                    setErrorInternal(exception);
+                }
             }
         }
 
+        private void setErrorInternal(Exception error) {
+            Log.e(MtpDocumentsProvider.TAG, "Error in DocumentLoader thread", error);
+            mError = error;
+            mNumLoaded = 0;
+        }
+
         private Uri createUri() {
             return DocumentsContract.buildChildDocumentsUri(
                     MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId);
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index 3faa8f4..cf1e1f6 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -27,9 +27,9 @@
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Root;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 
+import java.io.FileNotFoundException;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -56,11 +56,12 @@
 
     /**
      * Puts device information to database.
+     *
      * @return If device is added to the database.
+     * @throws FileNotFoundException
      */
-    synchronized boolean putDeviceDocument(MtpDeviceRecord device) {
+    synchronized boolean putDeviceDocument(MtpDeviceRecord device) throws FileNotFoundException {
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
-        Preconditions.checkState(mMappingMode.containsKey(/* no parent for root */ null));
         database.beginTransaction();
         try {
             final ContentValues[] valuesList = new ContentValues[1];
@@ -69,11 +70,11 @@
             extraValuesList[0] = new ContentValues();
             MtpDatabase.getDeviceDocumentValues(valuesList[0], extraValuesList[0], device);
             final boolean changed = putDocuments(
+                    null,
                     valuesList,
                     extraValuesList,
                     COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
                     EMPTY_ARGS,
-                    /* heuristic */ false,
                     COLUMN_DEVICE_ID);
             database.setTransactionSuccessful();
             return changed;
@@ -84,24 +85,24 @@
 
     /**
      * Puts root information to database.
+     *
      * @param parentDocumentId Document ID of device document.
      * @param roots List of root information.
      * @return If roots are added or removed from the database.
+     * @throws FileNotFoundException
      */
-    synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots) {
+    synchronized boolean putStorageDocuments(String parentDocumentId, MtpRoot[] roots)
+            throws FileNotFoundException {
         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:
@@ -116,11 +117,11 @@
                         valuesList[i], extraValuesList[i], parentDocumentId, roots[i]);
             }
             final boolean changed = putDocuments(
+                    parentDocumentId,
                     valuesList,
                     extraValuesList,
                     COLUMN_PARENT_DOCUMENT_ID + "=?",
                     strings(parentDocumentId),
-                    heuristic,
                     mapColumn);
 
             database.setTransactionSuccessful();
@@ -132,21 +133,21 @@
 
     /**
      * Puts document information to database.
+     *
      * @param deviceId Device ID
      * @param parentId Parent document ID.
      * @param documents List of document information.
+     * @throws FileNotFoundException
      */
-    synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
-        final boolean heuristic;
+    synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents)
+            throws FileNotFoundException {
         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:
@@ -159,21 +160,18 @@
                     valuesList[i], deviceId, parentId, documents[i]);
         }
         putDocuments(
+                parentId,
                 valuesList,
                 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);
@@ -193,9 +191,9 @@
      * a corresponding existing row. Otherwise it does heuristic.
      *
      * @param parentDocumentId Parent document ID or NULL for root documents.
+     * @throws FileNotFoundException
      */
-    void startAddingDocuments(@Nullable String parentDocumentId) {
-        Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId));
+    void startAddingDocuments(@Nullable String parentDocumentId) throws FileNotFoundException {
         final String selection;
         final String[] args;
         if (parentDocumentId != null) {
@@ -209,10 +207,8 @@
         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)));
+            getParentOrHaltMapping(parentDocumentId);
+            Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId));
 
             // Set all documents as invalidated.
             final ContentValues values = new ContentValues();
@@ -241,24 +237,27 @@
      * {@link #stopAddingDocuments(String)} turns the pending rows into 'valid'
      * rows. If the methods adds rows to database, it updates valueList with correct document ID.
      *
+     * @param parentId Parent document ID.
      * @param valuesList Values for documents to be stored in the database.
      * @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.
+     * @return Whether it adds at least one new row that is not mapped with existing document ID.
+     * @throws FileNotFoundException When parentId is not registered in the database.
      */
     private boolean putDocuments(
+            String parentId,
             ContentValues[] valuesList,
             @Nullable ContentValues[] rootExtraValuesList,
             String selection,
             String[] args,
-            boolean heuristic,
-            String mappingKey) {
+            String mappingKey) throws FileNotFoundException {
         final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
         boolean added = false;
         database.beginTransaction();
         try {
+            getParentOrHaltMapping(parentId);
+            Preconditions.checkState(mMappingMode.containsKey(parentId));
             for (int i = 0; i < valuesList.length; i++) {
                 final ContentValues values = valuesList[i];
                 final ContentValues rootExtraValues;
@@ -285,7 +284,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 +292,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.
@@ -320,11 +316,12 @@
      * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
      * If the database does not find corresponding 'invalidated' document, it just removes
      * 'invalidated' document from the database.
+     *
      * @param parentId Parent document ID or null for root documents.
      * @return Whether the methods adds or removed visible rows.
+     * @throws FileNotFoundException
      */
-    boolean stopAddingDocuments(@Nullable String parentId) {
-        Preconditions.checkState(mMappingMode.containsKey(parentId));
+    boolean stopAddingDocuments(@Nullable String parentId) throws FileNotFoundException {
         final String selection;
         final String[] args;
         if (parentId != null) {
@@ -334,86 +331,15 @@
             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();
+            getParentOrHaltMapping(parentId);
+            Preconditions.checkState(mMappingMode.containsKey(parentId));
+            mMappingMode.remove(parentId);
 
             boolean changed = false;
-
             // Delete all invalidated rows that cannot be mapped.
             if (mDatabase.deleteDocumentsAndRootsRecursively(
                     COLUMN_ROW_STATE + " = ? AND " + selection,
@@ -421,18 +347,6 @@
                 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 {
@@ -441,38 +355,23 @@
     }
 
     /**
-     * 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.
+     * Returns the parent identifier from parent document ID if the parent ID is found in the
+     * database. Otherwise it halts mapping and throws FileNotFoundException.
+     *
+     * @param parentId Parent document ID
+     * @return Parent identifier
+     * @throws FileNotFoundException
      */
-    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();
+    private @Nullable Identifier getParentOrHaltMapping(
+            @Nullable String parentId) throws FileNotFoundException {
+        if (parentId == null) {
+            return null;
         }
-    }
-
-    /**
-     * 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";
+        try {
+            return mDatabase.createIdentifier(parentId);
+        } catch (FileNotFoundException error) {
+            mMappingMode.remove(parentId);
+            throw error;
+        }
     }
 }
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/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index a4c3cf4..cec2b4d 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -22,6 +22,7 @@
 import android.provider.DocumentsContract;
 import android.util.Log;
 
+import java.io.FileNotFoundException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -123,14 +124,22 @@
 
                 // Update devices.
                 final MtpDeviceRecord[] devices = mManager.getDevices();
-                mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */);
-                for (final MtpDeviceRecord device : devices) {
-                    if (mDatabase.getMapper().putDeviceDocument(device)) {
+                try {
+                    mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */);
+                    for (final MtpDeviceRecord device : devices) {
+                        if (mDatabase.getMapper().putDeviceDocument(device)) {
+                            changed = true;
+                        }
+                    }
+                    if (mDatabase.getMapper().stopAddingDocuments(
+                            null /* parentDocumentId */)) {
                         changed = true;
                     }
-                }
-                if (mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */)) {
-                    changed = true;
+                } catch (FileNotFoundException exception) {
+                    // The top root (ID is null) must exist always.
+                    // FileNotFoundException is unexpected.
+                    Log.e(MtpDocumentsProvider.TAG, "Unexpected FileNotFoundException", exception);
+                    throw new AssertionError("Unexpected exception for the top parent", exception);
                 }
 
                 // Update roots.
@@ -139,12 +148,17 @@
                     if (documentId == null) {
                         continue;
                     }
-                    mDatabase.getMapper().startAddingDocuments(documentId);
-                    if (mDatabase.getMapper().putStorageDocuments(documentId, device.roots)) {
-                        changed = true;
-                    }
-                    if (mDatabase.getMapper().stopAddingDocuments(documentId)) {
-                        changed = true;
+                    try {
+                        mDatabase.getMapper().startAddingDocuments(documentId);
+                        if (mDatabase.getMapper().putStorageDocuments(documentId, device.roots)) {
+                            changed = true;
+                        }
+                        if (mDatabase.getMapper().stopAddingDocuments(documentId)) {
+                            changed = true;
+                        }
+                    } catch (FileNotFoundException exception) {
+                        Log.e(MtpDocumentsProvider.TAG, "Parent document is gone.", exception);
+                        continue;
                     }
                 }
 
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index af1fed4..6e28e33 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -36,16 +36,23 @@
     private TestContentResolver mResolver;
     private DocumentLoader mLoader;
     final private Identifier mParentIdentifier = new Identifier(
-            0, 0, 0, "1", MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE);
+            0, 0, 0, "2", MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE);
 
     @Override
-    public void setUp() {
+    public void setUp() throws Exception {
         mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+
+        mDatabase.getMapper().startAddingDocuments(null);
+        mDatabase.getMapper().putDeviceDocument(
+                new MtpDeviceRecord(1, "Device", true, new MtpRoot[0], null, null));
+        mDatabase.getMapper().stopAddingDocuments(null);
+
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
                 new MtpRoot(0, 0, "Storage", 1000, 1000, "")
         });
-        mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+        mDatabase.getMapper().stopAddingDocuments("1");
+
         mManager = new BlockableTestMtpManager(getContext());
         mResolver = new TestContentResolver();
         mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index a49dc67..ea52957 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -138,8 +138,10 @@
     }
 
     public void testPutStorageDocuments() throws Exception {
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+        addTestDevice();
+
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
                 new MtpRoot(0, 1, "Storage", 1000, 2000, ""),
                 new MtpRoot(0, 2, "Storage", 2000, 4000, ""),
                 new MtpRoot(0, 3, "/@#%&<>Storage", 3000, 6000,"")
@@ -150,7 +152,7 @@
             assertEquals(3, cursor.getCount());
 
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
             assertEquals(1, getInt(cursor, COLUMN_STORAGE_ID));
             assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
@@ -165,11 +167,11 @@
                     MtpDatabaseConstants.DOCUMENT_TYPE_STORAGE, getInt(cursor, COLUMN_DOCUMENT_TYPE));
 
             cursor.moveToNext();
-            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.moveToNext();
-            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals("/@#%&<>Storage", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.close();
@@ -186,18 +188,21 @@
     }
 
     public void testPutChildDocuments() throws Exception {
-        mDatabase.getMapper().startAddingDocuments("parentId");
-        mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+        addTestDevice();
+        addTestStorage("1");
+
+        mDatabase.getMapper().startAddingDocuments("2");
+        mDatabase.getMapper().putChildDocuments(0, "2", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
                 createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
         });
 
-        final Cursor cursor = mDatabase.queryChildDocuments(COLUMN_NAMES, "parentId");
+        final Cursor cursor = mDatabase.queryChildDocuments(COLUMN_NAMES, "2");
         assertEquals(3, cursor.getCount());
 
         cursor.moveToNext();
-        assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+        assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
         assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
         assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
         assertEquals(100, getInt(cursor, COLUMN_OBJECT_HANDLE));
@@ -216,7 +221,7 @@
                 MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, getInt(cursor, COLUMN_DOCUMENT_TYPE));
 
         cursor.moveToNext();
-        assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+        assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
         assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
         assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
         assertEquals(101, getInt(cursor, COLUMN_OBJECT_HANDLE));
@@ -235,7 +240,7 @@
                 MtpDatabaseConstants.DOCUMENT_TYPE_OBJECT, getInt(cursor, COLUMN_DOCUMENT_TYPE));
 
         cursor.moveToNext();
-        assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+        assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
         assertEquals(0, getInt(cursor, COLUMN_DEVICE_ID));
         assertEquals(0, getInt(cursor, COLUMN_STORAGE_ID));
         assertEquals(102, getInt(cursor, COLUMN_OBJECT_HANDLE));
@@ -263,8 +268,10 @@
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
 
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+        // Add device and two storages.
+        addTestDevice();
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
                 new MtpRoot(0, 100, "Storage A", 1000, 0, ""),
                 new MtpRoot(0, 101, "Storage B", 1001, 0, "")
         });
@@ -273,34 +280,37 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(100, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(101, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
+        // Clear mapping and add a device.
         mDatabase.getMapper().clearMapping();
+        addTestDevice();
 
         {
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
-            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.close();
         }
 
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+        // Add two storages, but one's name is different from previous one.
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
                 new MtpRoot(0, 200, "Storage A", 2000, 0, ""),
                 new MtpRoot(0, 202, "Storage C", 2002, 0, "")
         });
@@ -309,11 +319,11 @@
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(3, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
-            assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_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));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
@@ -323,13 +333,13 @@
             cursor.close();
         }
 
-        mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+        mDatabase.getMapper().stopAddingDocuments("1");
 
         {
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
             cursor.moveToNext();
@@ -346,69 +356,64 @@
                 MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
-        mDatabase.getMapper().startAddingDocuments("parentId");
-        mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+
+        addTestDevice();
+        addTestStorage("1");
+
+        mDatabase.getMapper().startAddingDocuments("2");
+        mDatabase.getMapper().putChildDocuments(0, "2", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
                 createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
         });
         mDatabase.getMapper().clearMapping();
 
+        addTestDevice();
+        addTestStorage("1");
+
         {
-            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "2");
             assertEquals(3, cursor.getCount());
 
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
             assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.moveToNext();
-            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
             assertEquals("image.jpg", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.moveToNext();
-            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
             assertEquals("music.mp3", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.close();
         }
 
-        mDatabase.getMapper().startAddingDocuments("parentId");
-        mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
+        mDatabase.getMapper().startAddingDocuments("2");
+        mDatabase.getMapper().putChildDocuments(0, "2", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                 createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
         });
+        mDatabase.getMapper().stopAddingDocuments("2");
 
         {
-            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
-            assertEquals(4, cursor.getCount());
-
-            cursor.moveToPosition(3);
-            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
-            assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
-            assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
-
-            cursor.close();
-        }
-
-        mDatabase.getMapper().stopAddingDocuments("parentId");
-
-        {
-            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "2");
             assertEquals(2, cursor.getCount());
 
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(200, getInt(cursor, COLUMN_OBJECT_HANDLE));
             assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));
 
             cursor.moveToNext();
-            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(6, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
             assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
+
             cursor.close();
         }
     }
@@ -424,10 +429,10 @@
                 Root.COLUMN_AVAILABLE_BYTES
         };
         mDatabase.getMapper().startAddingDocuments(null);
-        mDatabase.getMapper().putDeviceDocument(
-                new MtpDeviceRecord(0, "Device A", true, new MtpRoot[0], null, null));
-        mDatabase.getMapper().putDeviceDocument(
-                new MtpDeviceRecord(1, "Device B", true, new MtpRoot[0], null, null));
+        mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+                0, "Device A", true, new MtpRoot[0], null, null));
+        mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+                1, "Device B", true, new MtpRoot[0], null, null));
         mDatabase.getMapper().stopAddingDocuments(null);
 
         mDatabase.getMapper().startAddingDocuments("1");
@@ -467,6 +472,13 @@
 
         mDatabase.getMapper().clearMapping();
 
+        mDatabase.getMapper().startAddingDocuments(null);
+        mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+                0, "Device A", true, new MtpRoot[0], null, null));
+        mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+                1, "Device B", true, new MtpRoot[0], null, null));
+        mDatabase.getMapper().stopAddingDocuments(null);
+
         mDatabase.getMapper().startAddingDocuments("1");
         mDatabase.getMapper().startAddingDocuments("2");
         mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
@@ -511,45 +523,71 @@
                 MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
         };
 
-        mDatabase.getMapper().startAddingDocuments("parentId1");
-        mDatabase.getMapper().startAddingDocuments("parentId2");
-        mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+        // Add device, storage, and two directories.
+        addTestDevice();
+        addTestStorage("1");
+        mDatabase.getMapper().startAddingDocuments("2");
+        mDatabase.getMapper().putChildDocuments(0, "2", new MtpObjectInfo[] {
+                createDocument(50, "A", MtpConstants.FORMAT_ASSOCIATION, 0),
+                createDocument(51, "B", MtpConstants.FORMAT_ASSOCIATION, 0),
+        });
+        mDatabase.getMapper().stopAddingDocuments("2");
+
+        // Put note.txt in each directory.
+        mDatabase.getMapper().startAddingDocuments("3");
+        mDatabase.getMapper().startAddingDocuments("4");
+        mDatabase.getMapper().putChildDocuments(0, "3", new MtpObjectInfo[] {
                 createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+        mDatabase.getMapper().putChildDocuments(0, "4", new MtpObjectInfo[] {
                 createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
+
+        // Clear mapping.
         mDatabase.getMapper().clearMapping();
 
-        mDatabase.getMapper().startAddingDocuments("parentId1");
-        mDatabase.getMapper().startAddingDocuments("parentId2");
-        mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
+        // Add device, storage, and two directories again.
+        addTestDevice();
+        addTestStorage("1");
+        mDatabase.getMapper().startAddingDocuments("2");
+        mDatabase.getMapper().putChildDocuments(0, "2", new MtpObjectInfo[] {
+                createDocument(50, "A", MtpConstants.FORMAT_ASSOCIATION, 0),
+                createDocument(51, "B", MtpConstants.FORMAT_ASSOCIATION, 0),
+        });
+        mDatabase.getMapper().stopAddingDocuments("2");
+
+        // Add note.txt in each directory again.
+        mDatabase.getMapper().startAddingDocuments("3");
+        mDatabase.getMapper().startAddingDocuments("4");
+        mDatabase.getMapper().putChildDocuments(0, "3", new MtpObjectInfo[] {
                 createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
+        mDatabase.getMapper().putChildDocuments(0, "4", new MtpObjectInfo[] {
                 createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
         });
-        mDatabase.getMapper().stopAddingDocuments("parentId1");
+        mDatabase.getMapper().stopAddingDocuments("3");
+        mDatabase.getMapper().stopAddingDocuments("4");
 
+        // Check if the two note.txt are mapped correctly.
         {
-            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "3");
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(200, getInt(cursor, COLUMN_OBJECT_HANDLE));
             cursor.close();
         }
         {
-            final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId2");
+            final Cursor cursor = mDatabase.queryChildDocuments(columns, "4");
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
-            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
-            assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
+            assertEquals(6, getInt(cursor, COLUMN_DOCUMENT_ID));
+            assertEquals(201, getInt(cursor, COLUMN_OBJECT_HANDLE));
             cursor.close();
         }
     }
 
-    public void testClearMtpIdentifierBeforeResolveRootDocuments() {
+    public void testClearMtpIdentifierBeforeResolveRootDocuments() throws Exception {
         final String[] columns = new String[] {
                 DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                 MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -561,8 +599,8 @@
         };
 
         mDatabase.getMapper().startAddingDocuments(null);
-        mDatabase.getMapper().putDeviceDocument(
-                new MtpDeviceRecord(0, "Device", false,  new MtpRoot[0], null, null));
+        mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+                0, "Device", false,  new MtpRoot[0], null, null));
         mDatabase.getMapper().stopAddingDocuments(null);
 
         mDatabase.getMapper().startAddingDocuments("1");
@@ -571,12 +609,28 @@
         });
         mDatabase.getMapper().clearMapping();
 
+        mDatabase.getMapper().startAddingDocuments(null);
+        mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+                0, "Device", false,  new MtpRoot[0], null, null));
+        mDatabase.getMapper().stopAddingDocuments(null);
+
+        try (final Cursor cursor = mDatabase.queryRoots(resources, rootColumns)) {
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("1", getString(cursor, Root.COLUMN_ROOT_ID));
+        }
+
         mDatabase.getMapper().startAddingDocuments("1");
         mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
                 new MtpRoot(0, 200, "Storage", 2000, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
 
+        mDatabase.getMapper().startAddingDocuments(null);
+        mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+                0, "Device", false,  new MtpRoot[0], null, null));
+        mDatabase.getMapper().stopAddingDocuments(null);
+
         mDatabase.getMapper().startAddingDocuments("1");
         mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
                 new MtpRoot(0, 300, "Storage", 3000, 0, ""),
@@ -609,38 +663,47 @@
                 DocumentsContract.Document.COLUMN_DISPLAY_NAME
         };
 
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
-                new MtpRoot(0, 100, "Storage", 0, 0, ""),
-        });
+        // Add a device and a storage.
+        addTestDevice();
+        addTestStorage("1");
+
+        // Disconnect devices.
         mDatabase.getMapper().clearMapping();
 
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+        // Add a device and two storages that has same name.
+        addTestDevice();
+        mDatabase.getMapper().startAddingDocuments("1");
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
                 new MtpRoot(0, 200, "Storage", 2000, 0, ""),
                 new MtpRoot(0, 201, "Storage", 2001, 0, ""),
         });
-        mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+        mDatabase.getMapper().stopAddingDocuments("1");
 
         {
             final Cursor cursor = mDatabase.queryRootDocuments(columns);
             assertEquals(2, cursor.getCount());
+
+            // First storage reuse document ID of previous storage.
             cursor.moveToNext();
+            // One reuses exisitng document ID 1.
             assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+
+            // Second one has new document ID.
             cursor.moveToNext();
             assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
             assertEquals(201, getInt(cursor, COLUMN_STORAGE_ID));
             assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
+
             cursor.close();
         }
     }
 
-    public void testReplaceExistingRoots() {
+    public void testReplaceExistingRoots() throws Exception {
         mDatabase.getMapper().startAddingDocuments(null);
-        mDatabase.getMapper().putDeviceDocument(
-                new MtpDeviceRecord(0, "Device", true, new MtpRoot[0], null, null));
+        mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+                0, "Device", true, new MtpRoot[0], null, null));
         mDatabase.getMapper().stopAddingDocuments(null);
 
         // The client code should be able to replace existing rows with new information.
@@ -686,66 +749,62 @@
         }
     }
 
-    public void testFailToReplaceExisitingUnmappedRoots() {
+    public void testFailToReplaceExisitingUnmappedRoots() throws Exception {
         // The client code should not be able to replace rows before resolving 'unmapped' rows.
         // Add one.
-        mDatabase.getMapper().startAddingDocuments(null);
-        mDatabase.getMapper().putDeviceDocument(
-                new MtpDeviceRecord(0, "Device", true, new MtpRoot[0], null, null));
-        mDatabase.getMapper().stopAddingDocuments(null);
-
+        addTestDevice();
         mDatabase.getMapper().startAddingDocuments("1");
         mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
                 new MtpRoot(0, 100, "Storage A", 0, 0, ""),
         });
         mDatabase.getMapper().clearMapping();
-        final Cursor oldCursor = mDatabase.queryRoots(resources, strings(Root.COLUMN_ROOT_ID));
-        assertEquals(1, oldCursor.getCount());
 
-        // Add one.
-        mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
-                new MtpRoot(0, 101, "Storage B", 1000, 1000, ""),
-        });
-        // Add one more before resolving unmapped documents.
-        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
-                new MtpRoot(0, 102, "Storage B", 1000, 1000, ""),
-        });
-        mDatabase.getMapper().stopAddingDocuments("1");
+        addTestDevice();
+        try (final Cursor oldCursor =
+                mDatabase.queryRoots(resources, strings(Root.COLUMN_ROOT_ID))) {
+            assertEquals(1, oldCursor.getCount());
+            oldCursor.moveToNext();
+            assertEquals("1", getString(oldCursor, Root.COLUMN_ROOT_ID));
 
-        // Because the roots shares the same name, the roots should have new IDs.
-        final Cursor newCursor = mDatabase.queryChildDocuments(
-                strings(Document.COLUMN_DOCUMENT_ID), "1");
-        assertEquals(2, newCursor.getCount());
-        oldCursor.moveToNext();
-        newCursor.moveToNext();
-        assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
-        newCursor.moveToNext();
-        assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
+            // Add one.
+            mDatabase.getMapper().startAddingDocuments("1");
+            mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                    new MtpRoot(0, 101, "Storage B", 1000, 1000, ""),
+            });
+            // Add one more before resolving unmapped documents.
+            mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
+                    new MtpRoot(0, 102, "Storage B", 1000, 1000, ""),
+            });
+            mDatabase.getMapper().stopAddingDocuments("1");
 
-        oldCursor.close();
-        newCursor.close();
+            // Because the roots shares the same name, the roots should have new IDs.
+            try (final Cursor newCursor = mDatabase.queryChildDocuments(
+                    strings(Document.COLUMN_DOCUMENT_ID), "1")) {
+                assertEquals(2, newCursor.getCount());
+                newCursor.moveToNext();
+                assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
+                newCursor.moveToNext();
+                assertFalse(oldCursor.getString(0).equals(newCursor.getString(0)));
+            }
+        }
     }
 
-    public void testQueryDocuments() {
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
-                new MtpRoot(0, 100, "Storage A", 0, 0, ""),
-        });
-        mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+    public void testQueryDocuments() throws Exception {
+        addTestDevice();
+        addTestStorage("1");
 
-        final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME));
+        final Cursor cursor = mDatabase.queryDocument("2", strings(Document.COLUMN_DISPLAY_NAME));
         assertEquals(1, cursor.getCount());
         cursor.moveToNext();
-        assertEquals("Storage A", getString(cursor, Document.COLUMN_DISPLAY_NAME));
+        assertEquals("Storage", getString(cursor, Document.COLUMN_DISPLAY_NAME));
         cursor.close();
     }
 
-    public void testQueryRoots() {
+    public void testQueryRoots() throws Exception {
         // Add device document.
         mDatabase.getMapper().startAddingDocuments(null);
-        mDatabase.getMapper().putDeviceDocument(
-                new MtpDeviceRecord(0, "Device", false, new MtpRoot[0], null, null));
+        mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+                0, "Device", false, new MtpRoot[0], null, null));
         mDatabase.getMapper().stopAddingDocuments(null);
 
         // It the device does not have storages, it shows a device root.
@@ -790,38 +849,12 @@
     }
 
     public void testGetParentId() throws FileNotFoundException {
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
-                new MtpRoot(0, 100, "Storage A", 0, 0, ""),
-        });
-        mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+        addTestDevice();
 
         mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putChildDocuments(
-                0,
-                "1",
-                new MtpObjectInfo[] {
-                        createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
-                });
-        mDatabase.getMapper().stopAddingDocuments("1");
-
-        assertEquals("1", mDatabase.getParentIdentifier("2").mDocumentId);
-    }
-
-    public void testDeleteDocument() {
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
+        mDatabase.getMapper().putStorageDocuments("1", new MtpRoot[] {
                 new MtpRoot(0, 100, "Storage A", 0, 0, ""),
         });
-        mDatabase.getMapper().stopAddingDocuments("deviceDocId");
-
-        mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.getMapper().putChildDocuments(
-                0,
-                "1",
-                new MtpObjectInfo[] {
-                        createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024),
-                });
         mDatabase.getMapper().stopAddingDocuments("1");
 
         mDatabase.getMapper().startAddingDocuments("2");
@@ -833,12 +866,37 @@
                 });
         mDatabase.getMapper().stopAddingDocuments("2");
 
-        mDatabase.deleteDocument("2");
+        assertEquals("2", mDatabase.getParentIdentifier("3").mDocumentId);
+    }
+
+    public void testDeleteDocument() throws Exception {
+        addTestDevice();
+        addTestStorage("1");
+
+        mDatabase.getMapper().startAddingDocuments("2");
+        mDatabase.getMapper().putChildDocuments(
+                0,
+                "2",
+                new MtpObjectInfo[] {
+                        createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024),
+                });
+        mDatabase.getMapper().stopAddingDocuments("2");
+
+        mDatabase.getMapper().startAddingDocuments("3");
+        mDatabase.getMapper().putChildDocuments(
+                0,
+                "3",
+                new MtpObjectInfo[] {
+                        createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
+                });
+        mDatabase.getMapper().stopAddingDocuments("3");
+
+        mDatabase.deleteDocument("3");
 
         {
             // Do not query deleted documents.
             final Cursor cursor =
-                    mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
+                    mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "2");
             assertEquals(0, cursor.getCount());
             cursor.close();
         }
@@ -846,64 +904,58 @@
         {
             // Child document should be deleted also.
             final Cursor cursor =
-                    mDatabase.queryDocument("3", strings(Document.COLUMN_DOCUMENT_ID));
+                    mDatabase.queryDocument("4", strings(Document.COLUMN_DOCUMENT_ID));
             assertEquals(0, cursor.getCount());
             cursor.close();
         }
     }
 
-    public void testPutNewDocument() {
-        mDatabase.getMapper().startAddingDocuments("deviceDocId");
-        mDatabase.getMapper().putStorageDocuments("deviceDocId", new MtpRoot[] {
-                new MtpRoot(0, 100, "Storage A", 0, 0, ""),
-        });
-        mDatabase.getMapper().stopAddingDocuments("deviceDocId");
+    public void testPutNewDocument() throws Exception {
+        addTestDevice();
+        addTestStorage("1");
 
         assertEquals(
-                "2",
+                "3",
                 mDatabase.putNewDocument(
-                        0, "1", createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024)));
+                        0, "2", createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024)));
 
         {
             final Cursor cursor =
-                    mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
-            assertEquals(1, cursor.getCount());
-            cursor.moveToNext();
-            assertEquals("2", cursor.getString(0));
-            cursor.close();
-        }
-
-        // The new document should not be mapped with existing invalidated document.
-        mDatabase.getMapper().clearMapping();
-        mDatabase.getMapper().startAddingDocuments("1");
-        mDatabase.putNewDocument(
-                0,
-                "1",
-                createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024));
-        mDatabase.getMapper().stopAddingDocuments("1");
-
-        {
-            final Cursor cursor =
-                    mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1");
+                    mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "2");
             assertEquals(1, cursor.getCount());
             cursor.moveToNext();
             assertEquals("3", cursor.getString(0));
             cursor.close();
         }
+
+        // The new document should not be mapped with existing invalidated document.
+        mDatabase.getMapper().clearMapping();
+        mDatabase.getMapper().startAddingDocuments("2");
+        mDatabase.putNewDocument(
+                0,
+                "2",
+                createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024));
+        mDatabase.getMapper().stopAddingDocuments("2");
+
+        {
+            final Cursor cursor =
+                    mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "2");
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("4", cursor.getString(0));
+            cursor.close();
+        }
     }
 
-    public void testGetDocumentIdForDevice() {
-        mDatabase.getMapper().startAddingDocuments(null);
-        mDatabase.getMapper().putDeviceDocument(
-                new MtpDeviceRecord(100, "Device", true, new MtpRoot[0], null, null));
-        mDatabase.getMapper().stopAddingDocuments(null);
-        assertEquals("1", mDatabase.getDocumentIdForDevice(100));
+    public void testGetDocumentIdForDevice() throws Exception {
+        addTestDevice();
+        assertEquals("1", mDatabase.getDocumentIdForDevice(0));
     }
 
-    public void testGetClosedDevice() {
+    public void testGetClosedDevice() throws Exception {
         mDatabase.getMapper().startAddingDocuments(null);
         mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
-                0, "Device", /* opened is */ false , new MtpRoot[0], null, null));
+                0, "Device", /* opened is */ false, new MtpRoot[0], null, null));
         mDatabase.getMapper().stopAddingDocuments(null);
 
         final String[] columns = new String [] {
@@ -919,4 +971,19 @@
             assertTrue(cursor.isNull(2));
         }
     }
+
+    private void addTestDevice() throws FileNotFoundException {
+        mDatabase.getMapper().startAddingDocuments(null);
+        mDatabase.getMapper().putDeviceDocument(new MtpDeviceRecord(
+                0, "Device", /* opened is */ true, new MtpRoot[0], null, null));
+        mDatabase.getMapper().stopAddingDocuments(null);
+    }
+
+    private void addTestStorage(String parentId) throws FileNotFoundException {
+        mDatabase.getMapper().startAddingDocuments(parentId);
+        mDatabase.getMapper().putStorageDocuments(parentId, new MtpRoot[] {
+                new MtpRoot(0, 100, "Storage", 1024, 1024, ""),
+        });
+        mDatabase.getMapper().stopAddingDocuments(parentId);
+    }
 }
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 32699eb..6d047d0 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -416,6 +416,8 @@
     <string name="legacy_dhcp_client">Use legacy DHCP client</string>
     <!-- Setting Checkbox title whether to always keep cellular data active. [CHAR LIMIT=80] -->
     <string name="mobile_data_always_on">Cellular data always active</string>
+    <!-- Setting Checkbox title for disabling Bluetooth absolute volume -->
+    <string name="bluetooth_disable_absolute_volume">Disable absolute volume</string>
 
     <!-- setting Checkbox summary whether to show options for wireless display certification  -->
     <string name="wifi_display_certification_summary">Show options for wireless display certification</string>
@@ -456,6 +458,8 @@
     <string name="verify_apps_over_usb_title">Verify apps over USB</string>
     <!-- Summary of checkbox setting to perform package verification on apps installed over USB/ADT/ADB [CHAR LIMIT=NONE] -->
     <string name="verify_apps_over_usb_summary">Check apps installed via ADB/ADT for harmful behavior.</string>
+    <!-- Summary of checkbox for disabling Bluetooth absolute volume -->
+    <string name="bluetooth_disable_absolute_volume_summary">Disables the Bluetooth absolute volume feature in case of volume issues with remote devices such as unacceptably loud volume or lack of control.</string>
 
     <!-- Title of checkbox setting that enables the terminal app. [CHAR LIMIT=32] -->
     <string name="enable_terminal_title">Local terminal</string>
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/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/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/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/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/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 393edb6..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();
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 21330db..36ddbcf 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -3498,7 +3498,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/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index c8645b4..e47d514 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3427,6 +3427,18 @@
     }
 
     @Override
+    public @Nullable String getServicesSystemSharedLibraryPackageName() {
+        synchronized (mPackages) {
+            SharedLibraryEntry libraryEntry = mSharedLibraries.get(
+                    PackageManager.SYSTEM_SHARED_LIBRARY_SERVICES);
+            if (libraryEntry != null) {
+                return libraryEntry.apk;
+            }
+        }
+        return null;
+    }
+
+    @Override
     public FeatureInfo[] getSystemAvailableFeatures() {
         Collection<FeatureInfo> featSet;
         synchronized (mPackages) {
@@ -8220,7 +8232,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 +8313,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 +14409,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 +14423,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 +14449,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 +14468,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/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/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index ffb73f6..4e6d638 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -17,6 +17,7 @@
 package android.test.mock;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.PackageInstallObserver;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -806,6 +807,12 @@
         throw new UnsupportedOperationException();
     }
 
+    /** @hide */
+    @Override
+    public @Nullable String getServicesSystemSharedLibraryPackageName() {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public FeatureInfo[] getSystemAvailableFeatures() {
         throw new UnsupportedOperationException();
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/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 037ce57..5c20dfa 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -308,6 +308,11 @@
     }
 
     @Override
+    public String getServicesSystemSharedLibraryPackageName() {
+        return null;
+    }
+
+    @Override
     public FeatureInfo[] getSystemAvailableFeatures() {
         return new FeatureInfo[0];
     }
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