Merge "Test Roboto's vertical font metrics"
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 270f503..7ef43bb 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6233,6 +6233,20 @@
         public static final int VR_DISPLAY_MODE_OFF = 1;
 
         /**
+         * Whether CarrierAppUtils#disableCarrierAppsUntilPrivileged has been executed at least
+         * once.
+         *
+         * <p>This is used to ensure that we only take one pass which will disable apps that are not
+         * privileged (if any). From then on, we only want to enable apps (when a matching SIM is
+         * inserted), to avoid disabling an app that the user might actively be using.
+         *
+         * <p>Will be set to 1 once executed.
+         *
+         * @hide
+         */
+        public static final String CARRIER_APPS_HANDLED = "carrier_apps_handled";
+
+        /**
          * Whether parent user can access remote contact in managed profile.
          *
          * @hide
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8f480d6..88f2135 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -22965,7 +22965,7 @@
         /**
          * Last global system UI visibility reported by the window manager.
          */
-        int mGlobalSystemUiVisibility;
+        int mGlobalSystemUiVisibility = -1;
 
         /**
          * True if a view in this hierarchy has an OnSystemUiVisibilityChangeListener
diff --git a/core/java/android/webkit/WebMessagePort.java b/core/java/android/webkit/WebMessagePort.java
index 5f33c7b..54dd502 100644
--- a/core/java/android/webkit/WebMessagePort.java
+++ b/core/java/android/webkit/WebMessagePort.java
@@ -20,32 +20,32 @@
 import android.os.Handler;
 
 /**
- * The Java representation of the
+ * <p>The Java representation of the
  * <a href="https://html.spec.whatwg.org/multipage/comms.html#messageport">
- * HTML5 message ports.</a>
+ * HTML5 message ports.</a> </p>
  *
- * A Message port represents one endpoint of a Message Channel. In Android
+ * <p>A Message port represents one endpoint of a Message Channel. In Android
  * webview, there is no separate Message Channel object. When a message channel
  * is created, both ports are tangled to each other and started, and then
  * returned in a MessagePort array, see {@link WebView#createWebMessageChannel}
- * for creating a message channel.
+ * for creating a message channel. </p>
  *
- * When a message port is first created or received via transfer, it does not
+ * <p>When a message port is first created or received via transfer, it does not
  * have a WebMessageCallback to receive web messages. The messages are queued until
- * a WebMessageCallback is set.
+ * a WebMessageCallback is set. </p>
  *
- * A message port should be closed when it is not used by the embedder application
+ * <p>A message port should be closed when it is not used by the embedder application
  * anymore. A closed port cannot be transferred or cannot be reopened to send
- * messages. Close can be called multiple times.
+ * messages. Close can be called multiple times. </p>
  *
- * When a port is transferred to JS, it cannot be used to send or receive messages
+ * <p>When a port is transferred to JS, it cannot be used to send or receive messages
  * at the Java side anymore. Different from HTML5 Spec, a port cannot be transferred
  * if one of these has ever happened: i. a message callback was set, ii. a message was
  * posted on it. A transferred port cannot be closed by the application, since
- * the ownership is also transferred.
+ * the ownership is also transferred. </p>
  *
- * It is possible to transfer both ports of a channel to JS, for example for
- * communication between subframes.
+ * <p>It is possible to transfer both ports of a channel to JS, for example for
+ * communication between subframes.</p>
  */
 public abstract class WebMessagePort {
 
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index b174e33..be9bcfa 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -49,6 +49,8 @@
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LogWriter;
+import android.util.LongSparseArray;
+import android.util.LongSparseLongArray;
 import android.util.MutableInt;
 import android.util.PrintWriterPrinter;
 import android.util.Printer;
@@ -99,6 +101,7 @@
     private static final boolean DEBUG = false;
     public static final boolean DEBUG_ENERGY = false;
     private static final boolean DEBUG_ENERGY_CPU = DEBUG_ENERGY;
+    private static final boolean DEBUG_MEMORY = false;
     private static final boolean DEBUG_HISTORY = false;
     private static final boolean USE_OLD_HISTORY = false;   // for debugging.
 
@@ -144,6 +147,13 @@
     private final KernelUidCpuTimeReader mKernelUidCpuTimeReader = new KernelUidCpuTimeReader();
     private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
 
+    private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
+            = new KernelMemoryBandwidthStats();
+    private final LongSparseArray<SamplingTimer> mKernelMemoryStats = new LongSparseArray<>();
+    public LongSparseArray<SamplingTimer> getKernelMemoryStats() {
+        return mKernelMemoryStats;
+    }
+
     public interface BatteryCallback {
         public void batteryNeedsCpuUpdate();
         public void batteryPowerChanged(boolean onBattery);
@@ -2082,6 +2092,15 @@
         return kwlt;
     }
 
+    public SamplingTimer getKernelMemoryTimerLocked(long bucket) {
+        SamplingTimer kmt = mKernelMemoryStats.get(bucket);
+        if (kmt == null) {
+            kmt = new SamplingTimer(mClocks, mOnBatteryTimeBase);
+            mKernelMemoryStats.put(bucket, kmt);
+        }
+        return kmt;
+    }
+
     private int writeHistoryTag(HistoryTag tag) {
         Integer idxObj = mHistoryTagPool.get(tag);
         int idx;
@@ -7698,7 +7717,6 @@
                 NUM_BT_TX_LEVELS);
         mModemActivity = new ControllerActivityCounterImpl(mOnBatteryTimeBase,
                 ModemActivityInfo.TX_POWER_LEVELS);
-
         mMobileRadioActiveTimer = new StopwatchTimer(mClocks, null, -400, null, mOnBatteryTimeBase);
         mMobileRadioActivePerAppTimer = new StopwatchTimer(mClocks, null, -401, null,
                 mOnBatteryTimeBase);
@@ -8395,6 +8413,13 @@
             mKernelWakelockStats.clear();
         }
 
+        if (mKernelMemoryStats.size() > 0) {
+            for (int i = 0; i < mKernelMemoryStats.size(); i++) {
+                mOnBatteryTimeBase.remove(mKernelMemoryStats.valueAt(i));
+            }
+            mKernelMemoryStats.clear();
+        }
+
         if (mWakeupReasonStats.size() > 0) {
             for (SamplingTimer timer : mWakeupReasonStats.values()) {
                 mOnBatteryTimeBase.remove(timer);
@@ -9093,6 +9118,33 @@
     long mTempTotalCpuSystemTimeUs;
 
     /**
+     * Reads the newest memory stats from the kernel.
+     */
+    public void updateKernelMemoryBandwidthLocked() {
+        mKernelMemoryBandwidthStats.updateStats();
+        LongSparseLongArray bandwidthEntries = mKernelMemoryBandwidthStats.getBandwidthEntries();
+        final int bandwidthEntryCount = bandwidthEntries.size();
+        int index;
+        for (int i = 0; i < bandwidthEntryCount; i++) {
+            SamplingTimer timer;
+            if ((index = mKernelMemoryStats.indexOfKey(bandwidthEntries.keyAt(i))) >= 0) {
+                timer = mKernelMemoryStats.valueAt(index);
+            } else {
+                timer = new SamplingTimer(mClocks, mOnBatteryTimeBase);
+                mKernelMemoryStats.put(bandwidthEntries.keyAt(i), timer);
+            }
+            timer.update(bandwidthEntries.valueAt(i), 1);
+            if (DEBUG_MEMORY) {
+                Slog.d(TAG, String.format("Added entry %d and updated timer to: "
+                        + "mUnpluggedReportedTotalTime %d size %d", bandwidthEntries.keyAt(i),
+                        mKernelMemoryStats.get(
+                                bandwidthEntries.keyAt(i)).mUnpluggedReportedTotalTime,
+                        mKernelMemoryStats.size()));
+            }
+        }
+    }
+
+    /**
      * Read and distribute CPU usage across apps. If their are partial wakelocks being held
      * and we are on battery with screen off, we give more of the cpu time to those apps holding
      * wakelocks. If the screen is on, we just assign the actual cpu time an app used.
@@ -10372,6 +10424,14 @@
             }
         }
 
+        int NMS = in.readInt();
+        for (int ims = 0; ims < NMS; ims++) {
+            if (in.readInt() != 0) {
+                long kmstName = in.readLong();
+                getKernelMemoryTimerLocked(kmstName).readSummaryFromParcelLocked(in);
+            }
+        }
+
         final int NU = in.readInt();
         if (NU > 10000) {
             throw new ParcelFormatException("File corrupt: too many uids " + NU);
@@ -10727,6 +10787,18 @@
             }
         }
 
+        out.writeInt(mKernelMemoryStats.size());
+        for (int i = 0; i < mKernelMemoryStats.size(); i++) {
+            Timer kmt = mKernelMemoryStats.valueAt(i);
+            if (kmt != null) {
+                out.writeInt(1);
+                out.writeLong(mKernelMemoryStats.keyAt(i));
+                kmt.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
         final int NU = mUidStats.size();
         out.writeInt(NU);
         for (int iu = 0; iu < NU; iu++) {
@@ -11129,6 +11201,16 @@
             }
         }
 
+        mKernelMemoryStats.clear();
+        int nmt = in.readInt();
+        for (int imt = 0; imt < nmt; imt++) {
+            if (in.readInt() != 0) {
+                Long bucket = in.readLong();
+                SamplingTimer kmt = new SamplingTimer(mClocks, mOnBatteryTimeBase, in);
+                mKernelMemoryStats.put(bucket, kmt);
+            }
+        }
+
         mPartialTimers.clear();
         mFullTimers.clear();
         mWindowTimers.clear();
@@ -11287,6 +11369,18 @@
             out.writeInt(0);
         }
 
+        out.writeInt(mKernelMemoryStats.size());
+        for (int i = 0; i < mKernelMemoryStats.size(); i++) {
+            SamplingTimer kmt = mKernelMemoryStats.valueAt(i);
+            if (kmt != null) {
+                out.writeInt(1);
+                out.writeLong(mKernelMemoryStats.keyAt(i));
+                kmt.writeToParcel(out, uSecRealtime);
+            } else {
+                out.writeInt(0);
+            }
+        }
+
         if (inclUids) {
             int size = mUidStats.size();
             out.writeInt(size);
diff --git a/core/java/com/android/internal/os/KernelMemoryBandwidthStats.java b/core/java/com/android/internal/os/KernelMemoryBandwidthStats.java
new file mode 100644
index 0000000..b5915aa
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelMemoryBandwidthStats.java
@@ -0,0 +1,72 @@
+package com.android.internal.os;
+
+import android.os.StrictMode;
+import android.text.TextUtils;
+import android.util.LongSparseLongArray;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+
+/**
+ * Reads DDR time spent at various frequencies and stores the data.  Supports diff comparison with
+ * other KernelMemoryBandwidthStats objects. The sysfs file has the format:
+ *
+ * freq time_in_bucket ... time_in_bucket
+ *      ...
+ * freq time_in_bucket ... time_in_bucket
+ *
+ * where time is measured in nanoseconds.
+ */
+public class KernelMemoryBandwidthStats {
+    private static final String TAG = "KernelMemoryBandwidthStats";
+
+    final protected LongSparseLongArray mBandwidthEntries = new LongSparseLongArray();
+    private static final String mSysfsFile = "/sys/kernel/memory_state_time/show_stat";
+    private static final boolean DEBUG = false;
+
+    public void updateStats() {
+        StrictMode.ThreadPolicy policy = StrictMode.allowThreadDiskReads();
+        try (BufferedReader reader = new BufferedReader(new FileReader(mSysfsFile))) {
+            parseStats(reader);
+        } catch (IOException e) {
+            Slog.e(TAG, "Failed to read memory bandwidth: " + e.getMessage());
+            mBandwidthEntries.clear();
+        } finally {
+            StrictMode.setThreadPolicy(policy);
+        }
+    }
+
+    @VisibleForTesting
+    public void parseStats(BufferedReader reader) throws IOException {
+        String line;
+        TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter(' ');
+        mBandwidthEntries.clear();
+        while ((line = reader.readLine()) != null) {
+            splitter.setString(line);
+            splitter.next();
+            int bandwidth = 0;
+            int index;
+            do {
+                if ((index = mBandwidthEntries.indexOfKey(bandwidth)) >= 0) {
+                    mBandwidthEntries.put(bandwidth, mBandwidthEntries.valueAt(index)
+                            + Long.parseLong(splitter.next()) / 1000000);
+                } else {
+                    mBandwidthEntries.put(bandwidth, Long.parseLong(splitter.next()) / 1000000);
+                }
+                if (DEBUG) {
+                    Slog.d(TAG, String.format("bandwidth: %s time: %s", bandwidth,
+                            mBandwidthEntries.get(bandwidth)));
+                }
+                bandwidth++;
+            } while(splitter.hasNext());
+        }
+    }
+
+    public LongSparseLongArray getBandwidthEntries() {
+        return mBandwidthEntries;
+    }
+}
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 886265a..429131b 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -42,6 +42,8 @@
 import java.io.FileNotFoundException;
 import java.io.FileReader;
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Loads global system configuration info.
@@ -122,6 +124,11 @@
     // These are the permitted backup transport service components
     final ArraySet<ComponentName> mBackupTransportWhitelist = new ArraySet<>();
 
+    // These are the packages of carrier-associated apps which should be disabled until used until
+    // a SIM is inserted which grants carrier privileges to that carrier app.
+    final ArrayMap<String, List<String>> mDisabledUntilUsedPreinstalledCarrierAssociatedApps =
+            new ArrayMap<>();
+
     public static SystemConfig getInstance() {
         synchronized (SystemConfig.class) {
             if (sInstance == null) {
@@ -183,6 +190,10 @@
         return mBackupTransportWhitelist;
     }
 
+    public ArrayMap<String, List<String>> getDisabledUntilUsedPreinstalledCarrierAssociatedApps() {
+        return mDisabledUntilUsedPreinstalledCarrierAssociatedApps;
+    }
+
     SystemConfig() {
         // Read configuration from system
         readPermissions(Environment.buildPath(
@@ -476,6 +487,26 @@
                         }
                     }
                     XmlUtils.skipCurrentTag(parser);
+                } else if ("disabled-until-used-preinstalled-carrier-associated-app".equals(name)
+                        && allowAppConfigs) {
+                    String pkgname = parser.getAttributeValue(null, "package");
+                    String carrierPkgname = parser.getAttributeValue(null, "carrierAppPackage");
+                    if (pkgname == null || carrierPkgname == null) {
+                        Slog.w(TAG, "<disabled-until-used-preinstalled-carrier-associated-app"
+                                + " without package or carrierAppPackage in " + permFile + " at "
+                                + parser.getPositionDescription());
+                    } else {
+                        List<String> associatedPkgs =
+                                mDisabledUntilUsedPreinstalledCarrierAssociatedApps.get(
+                                        carrierPkgname);
+                        if (associatedPkgs == null) {
+                            associatedPkgs = new ArrayList<>();
+                            mDisabledUntilUsedPreinstalledCarrierAssociatedApps.put(
+                                    carrierPkgname, associatedPkgs);
+                        }
+                        associatedPkgs.add(pkgname);
+                    }
+                    XmlUtils.skipCurrentTag(parser);
                 } else {
                     XmlUtils.skipCurrentTag(parser);
                     continue;
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java b/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java
new file mode 100644
index 0000000..32317ee
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelMemoryBandwidthStatsTest.java
@@ -0,0 +1,80 @@
+package com.android.internal.os;
+
+import android.support.test.filters.SmallTest;
+import android.util.LongSparseLongArray;
+
+import junit.framework.TestCase;
+
+import org.junit.Assert;
+import org.mockito.Mockito;
+
+import java.io.BufferedReader;
+
+/**
+ * Tests for KernelMemoryBandwidthStats parsing and delta calculation, based on memory_state_time.
+ */
+public class KernelMemoryBandwidthStatsTest extends TestCase {
+
+    /**
+     * Standard example of parsing stats.
+     * @throws Exception
+     */
+    @SmallTest
+    public void testParseStandard() throws Exception {
+        KernelMemoryBandwidthStats stats = new KernelMemoryBandwidthStats();
+        BufferedReader mockStandardReader = Mockito.mock(BufferedReader.class);
+        Mockito.when(mockStandardReader.readLine()).thenReturn(
+                "99000000 0 0 0 0 0 0 0 0 0 0 0 0",
+                "149000000 0 0 0 0 0 0 0 0 0 0 0 0",
+                "199884800 7301000000 0 2000000 1000000 0 0 0 0 0 0 0 0",
+                "299892736 674000000 0 21000000 0 0 0 0 0 0 0 0 0",
+                "411959296 1146000000 0 221000000 1000000 0 0 0 0 0 0 0 0",
+                "546963456 744000000 0 420000000 0 0 0 1000000 0 0 0 0 0",
+                "680919040 182000000 0 1839000000 207000000 1000000 1000000 0 0 0 0 0 0",
+                "767950848 0 0 198000000 33000000 4000000 0 1000000 0 0 0 0 0",
+                "1016987648 0 0 339000000 362000000 3000000 0 0 0 0 0 0 16000000",
+                "1295908864 0 0 20000000 870000000 244000000 0 0 0 0 0 0 33000000",
+                "1554907136 0 0 6000000 32000000 631000000 115000000 0 0 0 1000000 0 0",
+                "1803943936 2496000000 0 17000000 2000000 377000000 1505000000 278000000 183000000 141000000 486000000 154000000 113000000", null);
+        stats.parseStats(mockStandardReader);
+        long[] expected = new long[] {12543L, 0L, 3083L, 1508L, 1260L, 1621L, 280L, 183L,
+                141L, 487L, 154L, 162L};
+        LongSparseLongArray array = stats.getBandwidthEntries();
+        for (int i = 2; i < array.size(); i++) {
+            assertEquals(i, array.keyAt(i));
+            assertEquals(expected[i], array.valueAt(i));
+        }
+        Mockito.verify(mockStandardReader, Mockito.times(13)).readLine();
+    }
+
+    /**
+     * When the stats are populated with zeroes (unsupported device), checks that the stats are
+     * zero.
+     * @throws Exception
+     */
+    @SmallTest
+    public void testParseBackwards() throws Exception {
+        KernelMemoryBandwidthStats zeroStats = new KernelMemoryBandwidthStats();
+        BufferedReader mockZeroReader = Mockito.mock(BufferedReader.class);
+        Mockito.when(mockZeroReader.readLine()).thenReturn(
+                "99000000 0 0 0 0 0 0 0 0 0 0 0 0",
+                "149000000 0 0 0 0 0 0 0 0 0 0 0 0",
+                "199884800 0 0 0 0 0 0 0 0 0 0 0 0",
+                "299892736 0 0 0 0 0 0 0 0 0 0 0 0",
+                "411959296 0 0 0 0 0 0 0 0 0 0 0 0",
+                "546963456 0 0 0 0 0 0 0 0 0 0 0 0",
+                "680919040 0 0 0 0 0 0 0 0 0 0 0 0",
+                "767950848 0 0 0 0 0 0 0 0 0 0 0 0",
+                "1016987648 0 0 0 0 0 0 0 0 0 0 0 0",
+                "1295908864 0 0 0 0 0 0 0 0 0 0 0 0",
+                "1554907136 0 0 0 0 0 0 0 0 0 0 0 0",
+                "1803943936 0 0 0 0 0 0 0 0 0 0 0 0", null);
+        zeroStats.parseStats(mockZeroReader);
+        long[] expected = new long[] {0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L};
+        LongSparseLongArray array = zeroStats.getBandwidthEntries();
+        for (int i = 0; i < array.size(); i++) {
+            assertEquals(expected[i], array.valueAt(i));
+        }
+        Mockito.verify(mockZeroReader, Mockito.times(13)).readLine();
+    }
+}
diff --git a/docs/html/_redirects.yaml b/docs/html/_redirects.yaml
index 2b94718..7d7282db 100644
--- a/docs/html/_redirects.yaml
+++ b/docs/html/_redirects.yaml
@@ -1207,4 +1207,12 @@
 - from: /preview/features/key-attestation.html
   to: /training/articles/security-key-attestation.html
 - from: /preview/features/security-config.html
-  to: /training/articles/security-config.html
\ No newline at end of file
+  to: /training/articles/security-config.html
+- from: /preview/features/picture-in-picture.html
+  to: /training/tv/playback/picture-in-picture.html
+- from: /preview/features/tv-recording-api.html
+  to: /training/tv/tif/content-recording.html
+- from: /preview/features/direct-boot.html
+  to: /training/articles/direct-boot.html
+- from: /preview/features/scoped-folder-access.html
+  to: /training/articles/scoped-directory-access.html
diff --git a/docs/html/guide/topics/data/data-storage.jd b/docs/html/guide/topics/data/data-storage.jd
index a745d00..770340b 100644
--- a/docs/html/guide/topics/data/data-storage.jd
+++ b/docs/html/guide/topics/data/data-storage.jd
@@ -252,6 +252,17 @@
 save to the external storage. All applications can read and write files placed on the external
 storage and the user can remove them.</p>
 
+<h3 id="ScopedDirAccess">Using Scoped Directory Access</h3>
+
+On Android 7.0 or later, if you need access to a specific directory on
+external storage, use scoped directory access. Scoped directory access
+simplifies how your application accesses standard external storage directories,
+such as the <code>Pictures</code> directory, and provides a simple
+permissions UI that clearly details what directory the application is
+requesting access to. For more details on scoped directory access, see
+<a href="{@docRoot}training/articles/scoped-directory-access.html">Using
+Scoped Directory Access</a>.
+
 <h3 id="ExternalPermissions">Getting access to external storage</h3>
 
 <p>In order to read or write files on the external storage, your app must acquire the
diff --git a/docs/html/guide/topics/ui/notifiers/notifications.jd b/docs/html/guide/topics/ui/notifiers/notifications.jd
index 6cd63f3..8c7e086 100644
--- a/docs/html/guide/topics/ui/notifiers/notifications.jd
+++ b/docs/html/guide/topics/ui/notifiers/notifications.jd
@@ -886,35 +886,38 @@
 
 <h3 id="controllingMedia">Controlling Media Playback on the Lock Screen</h3>
 
-<p>In Android 5.0 (API level 21) the lock screen no longer displays media controls
-based on the {@link android.media.RemoteControlClient}, which is now deprecated. Instead, use the
-{@link android.app.Notification.MediaStyle} template with the
-{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()}
-method, which converts actions into clickable icons.</p>
+<p>
+  In Android 5.0 (API level 21) the lock screen no longer displays media
+  controls based on the {@link android.media.RemoteControlClient}, which is
+  now deprecated. Instead, use the {@link
+  android.support.v7.app.NotificationCompat.MediaStyle} template with the
+  {@link
+  android.support.v4.app.NotificationCompat.Builder#addAction(android.support.v4.app.NotificationCompat.Action)
+  addAction()} method, which converts actions into clickable icons.
+</p>
 
-<p class="note"><strong>Note:</strong> The template and the
-{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()}
-method are not included in the support library, so these features run in Android 5.0 and higher
-only.</p>
-
-<p>To display media playback controls on the lock screen in Android 5.0, set the visibility
-to {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}, as described above. Then add
-the actions and set the {@link android.app.Notification.MediaStyle} template, as described in the
-following sample code:</p>
+<p>
+  To display media playback controls on the lock screen in Android 5.0, set
+  the visibility to {@link
+  android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}, as described
+  above. Then add the actions and set the {@link
+  android.support.v7.app.NotificationCompat.MediaStyle} template, as described
+  in the following sample code:
+</p>
 
 <pre>
-Notification notification = new Notification.Builder(context)
+Notification notification = new NotificationCompat.Builder(context)
     // Show controls on lock screen even when user hides sensitive content.
-    .setVisibility(Notification.VISIBILITY_PUBLIC)
+    .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
     .setSmallIcon(R.drawable.ic_stat_player)
     // Add media control buttons that invoke intents in your media service
     .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
     .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent)  // #1
     .addAction(R.drawable.ic_next, "Next", nextPendingIntent)     // #2
     // Apply the media style template
-    .setStyle(new Notification.MediaStyle()
-    .setShowActionsInCompactView(1 /* #1: pause button */)
-    .setMediaSession(mMediaSession.getSessionToken())
+    .setStyle(new NotificationCompat.MediaStyle()
+        .setShowActionsInCompactView(1 /* #1: pause button */)
+        .setMediaSession(mMediaSession.getSessionToken()))
     .setContentTitle("Wonderful music")
     .setContentText("My Awesome Band")
     .setLargeIcon(albumArtBitmap)
diff --git a/docs/html/preview/images/pip-active.png b/docs/html/images/android-7.0/pip-active.png
similarity index 100%
rename from docs/html/preview/images/pip-active.png
rename to docs/html/images/android-7.0/pip-active.png
Binary files differ
diff --git a/docs/html/preview/images/pip-button.png b/docs/html/images/android-7.0/pip-button.png
similarity index 100%
rename from docs/html/preview/images/pip-button.png
rename to docs/html/images/android-7.0/pip-button.png
Binary files differ
diff --git a/docs/html/images/android-7.0/scoped-directory-access-dont-ask.png b/docs/html/images/android-7.0/scoped-directory-access-dont-ask.png
new file mode 100644
index 0000000..6682948
--- /dev/null
+++ b/docs/html/images/android-7.0/scoped-directory-access-dont-ask.png
Binary files differ
diff --git a/docs/html/images/android-7.0/scoped-directory-access-dont-ask_2x.png b/docs/html/images/android-7.0/scoped-directory-access-dont-ask_2x.png
new file mode 100644
index 0000000..be717f9
--- /dev/null
+++ b/docs/html/images/android-7.0/scoped-directory-access-dont-ask_2x.png
Binary files differ
diff --git a/docs/html/images/android-7.0/scoped-directory-access-framed.png b/docs/html/images/android-7.0/scoped-directory-access-framed.png
new file mode 100644
index 0000000..e4d619a
--- /dev/null
+++ b/docs/html/images/android-7.0/scoped-directory-access-framed.png
Binary files differ
diff --git a/docs/html/images/android-7.0/scoped-directory-access-framed_2x.png b/docs/html/images/android-7.0/scoped-directory-access-framed_2x.png
new file mode 100644
index 0000000..fe17bce
--- /dev/null
+++ b/docs/html/images/android-7.0/scoped-directory-access-framed_2x.png
Binary files differ
diff --git a/docs/html/preview/features/tv-recording-api.jd b/docs/html/preview/features/tv-recording-api.jd
deleted file mode 100644
index 6619821..0000000
--- a/docs/html/preview/features/tv-recording-api.jd
+++ /dev/null
@@ -1,142 +0,0 @@
-page.title=TV Recording
-page.keywords=preview,sdk,tv,recording
-page.tags=androidn
-page.image=images/cards/card-nyc_2x.jpg
-
-@jd:body
-
-<div id="qv-wrapper">
-<div id="qv">
-  <h2>In this document</h2>
-  <ol>
-    <li><a href="#supporting">Indicating Support for Recording</a></li>
-    <li><a href="#recording">Recording a Session</a></li>
-    <li><a href="#errors">Handling Recording Errors</a></li>
-    <li><a href="#sessions">Managing Recorded Sessions</a></li>
-    <li><a href="#best">Best Practices</a></li>
-  </ol>
-</div>
-</div>
-
-<p>TV input services let the user pause and resume channel playback via
-time-shifting APIs. Android N expands on time-shifting
-by letting the user save multiple recorded sessions.</p>
-
-<p>Users can schedule recordings in advance, or start a recording as they watch
-a program. Once the system has saved a recording, the user can browse, manage,
-and play back the recording using the system TV app.</p>
-
-<p>If you want to provide recording functionality for your TV input service,
-you must indicate to the system that your app supports recording, implement
-the ability to record programs, handle and communicate any errors that occur
-during recording, and manage your recorded sessions.</p>
-
-<p class="note"><strong>Note:</strong> The Live Channels app does not yet
-provide a way for users to create or access recordings. Until changes are
-made to the Live Channels app, it may be difficult to fully test the recording
-experience for your TV input service.</p>
-
-<h2 id="supporting">Indicating Support for Recording</h2>
-
-<p>To tell the system that your TV input service supports recording, set
-the <code>android:canRecord</code> attribute in your service metadata XML file
-to <code>true</code>:
-</p>
-
-<pre>
-&lt;tv-input xmlns:android="http://schemas.android.com/apk/res/android"
-  <b>android:canRecord="true"</b>
-  android:setupActivity="com.example.sampletvinput.SampleTvInputSetupActivity" /&gt;
-</pre>
-
-<p>For more information on the service metadata file, see
-<a href="{@docRoot}training/tv/tif/tvinput.html#manifest">Declare Your TV Input
-Service in the Manifest</a>.
-</p>
-
-<p>Alternatively, you can indicate recording support in your code using
-these steps:</p>
-
-<ol>
-<li>In your <code>TvInputService.onCreate()</code> method, create a new
-<code>TvInputInfo</code> object using the <code>TvInputInfo.Builder</code>
-class.</li>
-<li>When creating the new <code>TvInputInfo</code> object, call
-<code>setCanRecord(true)</code> before calling <code>build()</code> to
-indicate your service supports recording.</li>
-<li>Register your <code>TvInputInfo</code> object with the system by calling
-<code>TvInputManager.updateTvInputInfo()</code>.</li>
-</ol>
-
-<h2 id="recording">Recording a Session</h2>
-
-<p>After your TV input service registers that it supports recording
-functionality, the system calls your
-<code>TvInputService.onCreateRecordingSession()</code> when it needs to access
-your app's recording implementation. Implement your own
-<code>TvInputService.RecordingSession</code> subclass and return it
-when the <code>onCreateRecordingSession()</code> callback
-fires. This subclass is responsible for switching to the correct channel data,
-recording the requested data, and communicating recording status and errors to
-the system.</p>
-
-<p>When the system calls <code>RecordingSession.onTune()</code>, passing in a
-channel URI, tune to the channel that the URI specifies. Notify the system that
-your app has tuned to the desired channel by calling <code>notifyTuned()</code>,
-or, if your app could not tune to the proper channel, call
-<code>notifyError()</code>.</p>
-
-<p>The system next invokes the <code>RecordingSession.onStartRecording()</code>
-callback. Your app must start recording immediately. When the system invokes
-this callback, it may provide a URI that contains information about the program
-that is about to be recorded. When the recording is done, you need to copy this
-data to the <code>RecordedPrograms</code> data table.</p>
-
-<p>Finally, the system calls <code>RecordingSession.onStopRecording()</code>.
-At this point, your app must stop recording immediately. You also need to
-create an entry in the <code>RecordedPrograms</code> table. This entry should
-include the recorded session data URI in the
-<code>RecordedPrograms.COLUMN_RECORDING_DATA_URI</code> column, and any program
-information that the system provided in the initial call to
-<code>onStartRecording()</code>.</p>
-
-<p>For more details on how to access the <code>RecordedPrograms</code> table
-see <a href="#sessions">Managing Recorded Sessions</a>.</p>
-
-<h2 id="errors">Handling Recording Errors</h2>
-
-<p>If an error occurs during recording, rendering the recorded data unusable,
-notify the system by calling <code>RecordingSession.notifyError()</code>.
-Similarly, you can call <code>notifyError()</code> after a recording session is
-created to let the system know that your app can no longer record sessions.</p>
-
-<p>If an error occurs during recording, but you'd like to provide a usable
-partial recording to users for playback, call
-<code>RecordingSession.notifyRecordingStopped()</code> to enable the system to
-use the partial session.</p>
-
-<h2 id="sessions">Managing Recorded Sessions</h2>
-
-<p>The system maintains information for all recorded sessions from all
-recording-capable channel apps in the <code>TvContract.RecordedPrograms</code>
-content provider table. This information is accessible via the
-<code>RecordedPrograms.Uri</code> content URI. Use content provider APIs to
-read, add, and delete entries from this table.</p>
-
-<p>For more information on working with content provider data see
-<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
-Content Provider Basics</a> .</p>
-
-<h2 id="best">Best Practices</h2>
-
-<p>TV devices may have limited storage, so use your best judgment when
-allocating storage to save recorded sessions. Use
-<code>RecordingCallback.onError(RECORDING_ERROR_INSUFFICIENT_SPACE)</code> when
-there isn't enough space to save a recorded session.</p>
-
-<p>When the user initiates recording, you should start recording data as soon
-as possible. To facilitate this, complete any up-front time-consuming tasks,
-like accessing and allocating storage space, when the system invokes the
-<code>onCreateRecordingSession()</code> callback. Doing so lets you start
-recording immediately when the <code>onStartRecording()</code> callback
-fires.</p>
diff --git a/docs/html/preview/images/scoped-folder-access-dont-ask.png b/docs/html/preview/images/scoped-folder-access-dont-ask.png
deleted file mode 100644
index 5c505d9..0000000
--- a/docs/html/preview/images/scoped-folder-access-dont-ask.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/images/scoped-folder-access-dont-ask_2x.png b/docs/html/preview/images/scoped-folder-access-dont-ask_2x.png
deleted file mode 100644
index 612b69f..0000000
--- a/docs/html/preview/images/scoped-folder-access-dont-ask_2x.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/images/scoped-folder-access-framed.png b/docs/html/preview/images/scoped-folder-access-framed.png
deleted file mode 100644
index 0169e41..0000000
--- a/docs/html/preview/images/scoped-folder-access-framed.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/preview/images/scoped-folder-access-framed_2x.png b/docs/html/preview/images/scoped-folder-access-framed_2x.png
deleted file mode 100644
index fd59ef1..0000000
--- a/docs/html/preview/images/scoped-folder-access-framed_2x.png
+++ /dev/null
Binary files differ
diff --git a/docs/html/training/_book.yaml b/docs/html/training/_book.yaml
index 0e2083a..23d9c9e 100644
--- a/docs/html/training/_book.yaml
+++ b/docs/html/training/_book.yaml
@@ -695,6 +695,8 @@
       path: /training/tv/playback/guided-step.html
     - title: Enabling Background Playback
       path: /training/tv/playback/options.html
+    - title: Adding Picture-in-picture
+      path: /training/tv/playback/picture-in-picture.html
   - title: Helping Users Find Content on TV
     path: /training/tv/discovery/index.html
     path_attributes:
@@ -724,6 +726,8 @@
       path: /training/tv/tif/channel.html
     - title: Managing User Interaction
       path: /training/tv/tif/ui.html
+    - title: Supporting Content Recording
+      path: /training/tv/tif/content-recording.html
   - title: TV Apps Checklist
     path: /training/tv/publishing/checklist.html
     path_attributes:
@@ -1388,6 +1392,16 @@
     path_attributes:
     - name: description
       value: How to create an application that enforces security policies on devices.
+  - title: Supporting Direct Boot
+    path: /training/articles/direct-boot.html
+    path_attributes:
+    - name: description
+      value: How use device encrypted storage during Direct Boot mode.
+  - title: Using Scoped Directory Access
+    path: /training/articles/scoped-directory-access.html
+    path_attributes:
+    - name: description
+      value: How to use scoped directory access to request access to external storage directories.
 
 - title: Best Practices for Permissions & Identifiers
   path: /training/best-permissions-ids.html
diff --git a/docs/html/preview/features/direct-boot.jd b/docs/html/training/articles/direct-boot.jd
similarity index 86%
rename from docs/html/preview/features/direct-boot.jd
rename to docs/html/training/articles/direct-boot.jd
index 60f6141..ea2686e 100644
--- a/docs/html/preview/features/direct-boot.jd
+++ b/docs/html/training/articles/direct-boot.jd
@@ -1,12 +1,10 @@
-page.title=Direct Boot
-page.keywords=preview,sdk,direct boot
-page.tags=androidn
-page.image=images/cards/card-nyc_2x.jpg
+page.title=Supporting Direct Boot
+page.keywords=direct boot
 
 @jd:body
 
-<div id="qv-wrapper">
-<div id="qv">
+<div id="tb-wrapper">
+<div id="tb">
   <h2>In this document</h2>
   <ol>
     <li><a href="#run">Requesting Access to Run During Direct Boot</a></li>
@@ -19,7 +17,7 @@
 </div>
 </div>
 
-<p>Android N runs in a secure, <i>Direct Boot</i> mode
+<p>Android 7.0 runs in a secure, <i>Direct Boot</i> mode
 when the device has been powered on but the user has not unlocked the
 device. To support this, the system provides two storage locations for data:</p>
 
@@ -63,21 +61,23 @@
 <code>android:directBootAware</code> attribute to true in your manifest.<p>
 
 <p>Encryption aware components can register to receive a
-<code>LOCKED_BOOT_COMPLETED</code> broadcast message from the
+{@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED
+ACTION_LOCKED_BOOT_COMPLETED} broadcast message from the
 system when the device has been restarted. At this point device encrypted
 storage is available, and your component can execute tasks that need to be
 run during Direct Boot mode, such as triggering a scheduled alarm.</p>
 
 <p>The following code snippet is an example of how to register a
 {@link android.content.BroadcastReceiver} as encryption aware, and add an
-intent filter for <code>LOCKED_BOOT_COMPLETED</code>, in the app manifest:</p>
+intent filter for {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED
+ACTION_LOCKED_BOOT_COMPLETED}, in the app manifest:</p>
 
 <pre>
 &lt;receiver
   android:directBootAware="true" &gt;
   ...
   &lt;intent-filter&gt;
-    &lt;action android:name="android.intent.action.LOCKED_BOOT_COMPLETED" /&gt;
+    &lt;action android:name="android.intent.action.ACTION_LOCKED_BOOT_COMPLETED" /&gt;
   &lt;/intent-filter&gt;
 &lt;/receiver&gt;
 </pre>
@@ -89,7 +89,8 @@
 
 <p>To access device encrypted storage, create a second
 {@link android.content.Context} instance by calling
-<code>Context.createDeviceProtectedStorageContext()</code>. All storage API
+{@link android.content.Context#createDeviceProtectedStorageContext
+Context.createDeviceProtectedStorageContext()}. All storage API
 calls made using this context access the device encrypted storage. The
 following example accesses the device encrypted storage and opens an existing
 app data file:</p>
@@ -120,7 +121,8 @@
 </p>
 <ul>
 <li>If your app has foreground processes that need immediate notification,
-listen for the {@code ACTION_USER_UNLOCKED} message.</li>
+listen for the {@link android.content.Intent#ACTION_USER_UNLOCKED
+ACTION_USER_UNLOCKED} message.</li>
 <li>If your app only uses background processes that can act on a delayed
 notification, listen for the
 {@link android.content.Intent#ACTION_BOOT_COMPLETED ACTION_BOOT_COMPLETED}
@@ -128,14 +130,17 @@
 </ul>
 
 <p>If the user has unlocked the device, you can find out by calling
-<code>UserManager.isUserUnlocked()</code>.</p>
+{@link android.os.UserManager#isUserUnlocked UserManager.isUserUnlocked()}.
+</p>
 
 <h2 id="migrating">Migrating Existing Data</h2>
 
 <p>If a user updates their device to use Direct Boot mode, you might have
 existing data that needs to get migrated to device encrypted storage. Use
-<code>Context.moveSharedPreferencesFrom()</code> and
-<code>Context.moveDatabaseFrom()</code> to migrate preference and database
+{@link android.content.Context#moveSharedPreferencesFrom
+Context.moveSharedPreferencesFrom()} and
+{@link android.content.Context#moveDatabaseFrom
+Context.moveDatabaseFrom()} to migrate preference and database
 data between credential encrypted storage and device encrypted storage.</p>
 
 <p>Use your best judgment when deciding what data to migrate from credential
@@ -146,13 +151,13 @@
 
 <h2 id="testing">Testing Your Encryption Aware App</h2>
 
-<p>Test your encryption aware app using the new Direct Boot mode. There are
+<p>Test your encryption aware app with Direct Boot mode enabled. There are
 two ways to enable Direct Boot.</p>
 
 <p class="caution"><strong>Caution:</strong> Enabling Direct Boot
 wipes all user data on the device.</p>
 
-<p>On supported devices with Android N installed, enable
+<p>On supported devices with Android 7.0 installed, enable
 Direct Boot by doing one of the following:</p>
 
 <ul>
@@ -194,14 +199,14 @@
 {@link android.app.admin.DevicePolicyManager#getStorageEncryptionStatus
 DevicePolicyManager.getStorageEncryptionStatus()} to check the current
 encryption status of the device. If your app is targeting an API level
-lower than Android N,
+lower than 24.0 (Android 7.0),
 {@link android.app.admin.DevicePolicyManager#getStorageEncryptionStatus
 getStorageEncryptionStatus()} will return
 {@link android.app.admin.DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE
 ENCRYPTION_STATUS_ACTIVE} if the device is either using full-disk encryption,
 or file-based encryption with Direct Boot. In both of these cases, data is
 always stored encrypted at rest. If your app is targeting an API level of
-Android N or higher,
+24.0 or higher,
 {@link android.app.admin.DevicePolicyManager#getStorageEncryptionStatus
 getStorageEncryptionStatus()} will return
 {@link android.app.admin.DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE
@@ -212,7 +217,7 @@
 with Direct Boot.</p>
 
 <p>If you build a device administration app
-that targets Android N, make sure to check for both
+that targets Android 7.0, make sure to check for both
 {@link android.app.admin.DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE
 ENCRYPTION_STATUS_ACTIVE} and
 {@link android.app.admin.DevicePolicyManager#ENCRYPTION_STATUS_ACTIVE_PER_USER
diff --git a/docs/html/preview/features/scoped-folder-access.jd b/docs/html/training/articles/scoped-directory-access.jd
similarity index 60%
rename from docs/html/preview/features/scoped-folder-access.jd
rename to docs/html/training/articles/scoped-directory-access.jd
index 06dd665..1e0bf39 100644
--- a/docs/html/preview/features/scoped-folder-access.jd
+++ b/docs/html/training/articles/scoped-directory-access.jd
@@ -1,11 +1,10 @@
-page.title=Scoped Directory Access
-page.keywords=preview,sdk,scoped directory access
-page.tags=androidn
+page.title=Using Scoped Directory Access
+page.keywords=scoped directory access
 
 @jd:body
 
-<div id="qv-wrapper">
-<div id="qv">
+<div id="tb-wrapper">
+<div id="tb">
   <h2>In this document</h2>
   <ol>
     <li><a href="#accessing">Accessing an External Storage Directory</a></li>
@@ -32,29 +31,37 @@
 external directory.</li>
 </ul>
 
-<p>Android N provides a new simplified API to access
-common external storage directories. </p>
+<p>Android 7.0 provides a simplified API to access common external storage
+directories.</p>
 
 <h2 id="accessing">Accessing an External Storage Directory</h2>
 
-<p>Use the <code>StorageManager</code> class to get the appropriate
-<code>StorageVolume</code> instance. Then, create an intent by calling the
-<code>StorageVolume.createAccessIntent()</code> method of that instance.
+<p>Use the {@link android.os.storage.StorageManager} class to get the
+appropriate {@link android.os.storage.StorageVolume} instance. Then, create
+an intent by calling the
+{@link android.os.storage.StorageVolume#createAccessIntent
+StorageVolume.createAccessIntent()} method of that instance.
 Use this intent to access external storage directories. To get a list of
-all available volumes, including removable media volumes, use
-<code>StorageManager.getVolumesList()</code>.</p>
+all available volumes, including removable media
+volumes, use {@link android.os.storage.StorageManager#getStorageVolumes
+StorageManager.getStorageVolumes()}.</p>
 
 <p>If you have information about a specific file, use
-<code>StorageManager.getStorageVolume(File)</code> to get the
-<code>StorageVolume</code> that contains the file. Call
-<code>createAccessIntent()</code> on this <code>StorageVolume</code> to access
+{@link android.os.storage.StorageManager#getStorageVolume
+StorageManager.getStorageVolume(File)} to get the
+{@link android.os.storage.StorageVolume} that contains the file. Call
+{@link android.os.storage.StorageVolume#createAccessIntent
+createAccessIntent()} on this
+{@link android.os.storage.StorageVolume} to access
 the external storage directory for the file.</p>
 
 <p>
 On secondary volumes, such as external SD cards, pass in null when calling
-<code>StorageVolume.createAccessIntent()</code> to request access to the entire
+{@link android.os.storage.StorageVolume#createAccessIntent
+createAccessIntent()} to request access to the entire
 volume, instead of a specific directory.
-<code>StorageVolume.createAccessIntent()</code> returns null if you pass in
+{@link android.os.storage.StorageVolume#createAccessIntent
+createAccessIntent()} returns null if you pass in
 null to the primary volume, or if you pass in an invalid directory name.
 </p>
 
@@ -63,7 +70,7 @@
 
 <pre>
 StorageManager sm = (StorageManager)getSystemService(Context.STORAGE_SERVICE);
-StorageVolume volume = sm.getPrimaryVolume();
+StorageVolume volume = sm.getPrimaryStorageVolume();
 Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES);
 startActivityForResult(intent, request_code);
 </pre>
@@ -71,25 +78,27 @@
 <p>The system attempts to grant access to the external directory, and if
 necessary confirms access with the user using a simplified UI:</p>
 
-<img src="{@docRoot}preview/images/scoped-folder-access-framed.png"
-srcset="{@docRoot}preview/images/scoped-folder-access-framed.png 1x,
-{@docRoot}preview/images/scoped-folder-access-framed_2x.png 2x" />
+<img src="{@docRoot}images/android-7.0/scoped-directory-access-framed.png"
+srcset="{@docRoot}images/android-7.0/scoped-directory-access-framed.png 1x,
+{@docRoot}images/android-7.0/scoped-directory-access-framed_2x.png 2x" />
 <p class="img-caption"><strong>Figure 1.</strong> An application requesting
 access to the Pictures directory.</p>
 
 <p>If the user grants access, the system calls your
-<code>onActivityResult()</code> override with a result code of
-<code>Activity.RESULT_OK</code>, and intent data that contains the URI. Use
+{@link android.app.Activity#onActivityResult onActivityResult()} override
+with a result code of {@link android.app.Activity#RESULT_OK
+RESULT_OK}, and intent data that contains the URI. Use
 the provided URI to access directory information, similar to using URIs
 returned by the
 <a href="{@docRoot}guide/topics/providers/document-provider.html">Storage
 Access Framework</a>.</p>
 
 <p>If the user doesn't grant access, the system calls your
-<code>onActivityResult()</code> override with a result code of
-<code>Activity.RESULT_CANCELED</code>, and null intent data.</p>
+{@link android.app.Activity#onActivityResult onActivityResult()} override
+with a result code of {@link android.app.Activity#RESULT_CANCELED
+RESULT_CANCELED}, and null intent data.</p>
 
-<p class="note"><b>Note</b>: Getting access to a specific external directory
+<p>Getting access to a specific external directory
 also gains access to subdirectories within that directory.</p>
 
 <h2 id="removable">Accessing a Directory on Removable Media</h2>
@@ -112,9 +121,9 @@
 
 <p>When the user mounts removable media, like an SD card, the system sends a
 {@link android.os.Environment#MEDIA_MOUNTED} notification. This notification
-provides a <code>StorageVolume</code> object in the intent data that you can
-use to access directories on the removable media. The following example
-accesses the <code>Pictures</code> directory on removable media:</p>
+provides a {@link android.os.storage.StorageVolume} object in the intent data
+that you can use to access directories on the removable media. The following
+example accesses the <code>Pictures</code> directory on removable media:</p>
 
 <pre>
 // BroadcastReceiver has already cached the MEDIA_MOUNTED
@@ -129,19 +138,22 @@
 
 <p>Where possible, persist the external directory access URI so you don't have
 to repeatedly ask the user for access. Once the user has granted access, call
-<code>getContentResolver().takePersistableUriPermssion()</code> with the
-directory access URI. The system will persist the URI and subsequent access
-requests will return <code>RESULT_OK</code> and not show confirmation UI to the
-user.</p>
+{@link android.content.Context#getContentResolver getContentResolver()} and
+with the returned {@link android.content.ContentResolver} call
+{@link android.content.ContentResolver#takePersistableUriPermission
+takePersistableUriPermission()} with the directory access URI. The system will
+persist the URI and subsequent access requests will return
+{@link android.app.Activity#RESULT_OK RESULT_OK} and not show confirmation
+UI to the user.</p>
 
 <p>If the user denies access to an external directory, do not immediately
 request access again. Repeatedly insisting on access results in a poor user
 experience. If a request is denied by the user, and the app requests access
 again, the UI displays a <b>Don't ask again</b> checkbox:</p>
 
-<img src="{@docRoot}preview/images/scoped-folder-access-dont-ask.png"
-srcset="{@docRoot}preview/images/scoped-folder-access-dont-ask.png 1x,
-{@docRoot}preview/images/scoped-folder-access-dont-ask_2x.png 2x" />
+<img src="{@docRoot}images/android-7.0/scoped-directory-access-dont-ask.png"
+srcset="{@docRoot}images/android-7.0/scoped-directory-access-dont-ask.png 1x,
+{@docRoot}images/android-7.0/scoped-directory-access-dont-ask_2x.png 2x" />
 <p class="img-caption"><strong>Figure 1.</strong> An application making a
 second request for access to removable media.</p>
 
diff --git a/docs/html/preview/features/picture-in-picture.jd b/docs/html/training/tv/playback/picture-in-picture.jd
similarity index 87%
rename from docs/html/preview/features/picture-in-picture.jd
rename to docs/html/training/tv/playback/picture-in-picture.jd
index 03a1768..e48ae48 100644
--- a/docs/html/preview/features/picture-in-picture.jd
+++ b/docs/html/training/tv/playback/picture-in-picture.jd
@@ -1,11 +1,14 @@
-page.title=Picture-in-picture
+page.title=Adding Picture-in-picture
 page.keywords=preview,sdk,PIP,Picture-in-picture
 page.tags=androidn
+helpoutsWidget=true
+
+trainingnavtop=true
 
 @jd:body
 
-<div id="qv-wrapper">
-<div id="qv">
+<div id="tb-wrapper">
+<div id="tb">
 
 <h2>In this document</h2>
 <ol>
@@ -31,12 +34,12 @@
 </div>
 </div>
 
-<p>In Android N, Android TV users can now watch a video
-in a pinned window in a corner of the screen when navigating within
-apps. Picture-in-picture (PIP) mode lets apps run a video
+<p>In Android 7.0, Android TV users can now watch a video
+in a pinned window in a corner of the screen when navigating within or
+between apps. Picture-in-picture (PIP) mode lets apps run a video
 activity in the pinned window while another activity continues in the
-background. The PIP window lets users multitask while using your app, which
-helps users be more productive.</p>
+background. The PIP window lets users multitask while using Android TV,
+which helps users be more productive.</p>
 
 <p>Your app can decide when to trigger PIP mode. Here are some examples of
 when to enter PIP mode:</p>
@@ -57,14 +60,14 @@
 PIP menu that lets them toggle the PIP window to full-screen, or close the PIP
 window, by holding down the <b>Home</b> button on the remote. If another
 video starts playing on the main screen, the PIP window is automatically
-closed. Users can also close the PIP window through Recents.</p>
+closed.</p>
 
-<img src="{@docRoot}preview/images/pip-active.png" />
+<img src="{@docRoot}images/android-7.0/pip-active.png" />
 <p class="img-caption"><strong>Figure 1.</strong> A Picture-in-picture
 video visible in a corner of the screen while the user browses content
 on the main screen.</p>
 
-<p>PIP leverages the multi-window APIs available in Android N to
+<p>PIP leverages the multi-window APIs available in Android 7.0 to
 provide the pinned video overlay window. To add PIP to your app, you need to
 register your activities that support PIP, switch your activity to PIP mode as
 needed, and make sure UI elements are hidden and video playback continues when
@@ -99,7 +102,8 @@
 <h2 id="pip_button">Switching Your Activity to Picture-in-picture</h2>
 
 When you need to switch your activity into PIP mode, call
-<code>Activity.enterPictureInPictureMode()</code>. The following example
+{@link android.app.Activity#enterPictureInPictureMode
+enterPictureInPictureMode()}. The following example
 switches to PIP mode when the user selects a dedicated PIP button on a media
 control bar:</p>
 
@@ -116,12 +120,13 @@
 <p>Adding a PIP button to your media control bar lets your user easily switch
 to PIP mode while controlling video playback.</p>
 
-<img src="{@docRoot}preview/images/pip-button.png" />
+<img src="{@docRoot}images/android-7.0/pip-button.png" />
 <p class="img-caption"><strong>Figure 1.</strong> A Picture-in-picture
 button on a media control bar.</p>
 
-<p>Android N includes a new
-<code>PlaybackControlsRow.PictureInPictureAction</code> class which defines
+<p>Android 7.0 includes a
+{@link android.support.v17.leanback.widget.PlaybackControlsRow.PictureInPictureAction
+PlaybackControlsRow.PictureInPictureAction} class which defines
 control bar PIP actions and uses the PIP icon.</p>
 
 <h2 id="handling_ui">Handling UI During Picture-in-picture</h2>
@@ -129,8 +134,10 @@
 <p>When your activity enters PIP mode, your activity should only show video
 playback. Remove UI elements before your activity enters PIP,
 and restore these elements when your activity becomes full-screen again.
-Override <code>Activity.onPictureInPictureModeChanged()</code> or
-<code>Fragment.onPictureInPictureModeChanged()</code> and enable or
+Override {@link android.app.Activity#onPictureInPictureModeChanged
+Activity.onPictureInPictureModeChanged()} or
+{@link android.app.Fragment#onPictureInPictureModeChanged
+Fragment.onPictureInPictureModeChanged()} and enable or
 disable your UI elements as needed, for example:</p>
 
 <pre>
@@ -154,7 +161,7 @@
 onPause()} method. Video playback should not be paused and should continue
 playing if the activity is paused due to PIP mode.</p>
 
-<p>In Android N, you should pause and resume video playback when the system
+<p>In Android 7.0, you should pause and resume video playback when the system
 calls your activity's {@link android.app.Activity#onStop onStop()} and
 {@link android.app.Activity#onStart onStart()}. By doing this, you can avoid
 having to check if your app is in PIP mode in
@@ -204,7 +211,7 @@
 </pre>
 
 <p>In your activity, override {@link android.app.Activity#onNewIntent
-Activity.onNewIntent()} and handle the new video, stopping any existing video
+onNewIntent()} and handle the new video, stopping any existing video
 playback if needed.</p>
 
 <h2 id="best">Best Practices</h2>
diff --git a/docs/html/training/tv/tif/content-recording.jd b/docs/html/training/tv/tif/content-recording.jd
new file mode 100644
index 0000000..ffdd14c
--- /dev/null
+++ b/docs/html/training/tv/tif/content-recording.jd
@@ -0,0 +1,171 @@
+page.title=Supporting Content Recording
+page.keywords=tv,recording,channel,tif
+page.tags=tv, tif
+helpoutsWidget=true
+
+trainingnavtop=true
+
+@jd:body
+
+<div id="tb-wrapper">
+<div id="tb">
+  <h2>In this document</h2>
+  <ol>
+    <li><a href="#supporting">Indicating Support for Recording</a></li>
+    <li><a href="#recording">Recording a Session</a></li>
+    <li><a href="#errors">Handling Recording Errors</a></li>
+    <li><a href="#sessions">Managing Recorded Sessions</a></li>
+    <li><a href="#best">Best Practices</a></li>
+  </ol>
+</div>
+</div>
+
+<p>TV input services let the user pause and resume channel playback via
+time-shifting APIs. Android 7.0 expands on time-shifting
+by letting the user save multiple recorded sessions.</p>
+
+<p>Users can schedule recordings in advance, or start a recording as they watch
+a program. Once the system has saved a recording, the user can browse, manage,
+and play back the recording using the system TV app.</p>
+
+<p>If you want to provide recording functionality for your TV input service,
+you must indicate to the system that your app supports recording, implement
+the ability to record programs, handle and communicate any errors that occur
+during recording, and manage your recorded sessions.</p>
+
+<p class="note"><strong>Note:</strong> The Live Channels app does not yet
+provide a way for users to create or access recordings. Until changes are
+made to the Live Channels app, it may be difficult to fully test the recording
+experience for your TV input service.</p>
+
+<h2 id="supporting">Indicating Support for Recording</h2>
+
+<p>To tell the system that your TV input service supports recording, set
+the <code>android:canRecord</code> attribute in your service metadata XML file
+to <code>true</code>:
+</p>
+
+<pre>
+&lt;tv-input xmlns:android="http://schemas.android.com/apk/res/android"
+  <b>android:canRecord="true"</b>
+  android:setupActivity="com.example.sampletvinput.SampleTvInputSetupActivity" /&gt;
+</pre>
+
+<p>For more information on the service metadata file, see
+<a href="{@docRoot}training/tv/tif/tvinput.html#manifest">Declare Your TV Input
+Service in the Manifest</a>.
+</p>
+
+<p>Alternatively, you can indicate recording support in your code using
+these steps:</p>
+
+<ol>
+<li>In your TV input service {@link android.app.Service#onCreate onCreate()}
+method, create a new {@link android.media.tv.TvInputInfo} object using the
+{@link android.media.tv.TvInputInfo.Builder TvInputInfo.Builder} class.</li>
+<li>When creating the new {@link android.media.tv.TvInputInfo} object, call
+{@link android.media.tv.TvInputInfo.Builder#setCanRecord
+setCanRecord(true)} before calling
+{@link android.media.tv.TvInputInfo.Builder#build build()} to indicate your
+service supports recording.</li>
+<li>Register your {@link android.media.tv.TvInputInfo} object with the
+system by calling
+{@link android.media.tv.TvInputManager#updateTvInputInfo
+TvInputManager.updateTvInputInfo()}.</li>
+</ol>
+
+<h2 id="recording">Recording a Session</h2>
+
+<p>After your TV input service registers that it supports recording
+functionality, the system calls your
+{@link android.media.tv.TvInputService#onCreateRecordingSession
+TvInputService.onCreateRecordingSession()} method when it needs to access
+your app's recording implementation. Implement your own
+{@link android.media.tv.TvInputService.RecordingSession
+TvInputService.RecordingSession} subclass and return it
+when the {@link android.media.tv.TvInputService#onCreateRecordingSession
+onCreateRecordingSession()} callback fires. This subclass is responsible
+for switching to the correct channel data, recording the requested data,
+and communicating recording status and errors to the system.</p>
+
+<p>When the system calls
+{@link android.media.tv.TvInputService.RecordingSession#onTune
+RecordingSession.onTune()}, passing in a channel URI, tune to the channel
+that the URI specifies. Notify the system that your app has tuned to the
+desired channel by calling
+{@link android.media.tv.TvInputService.RecordingSession#notifyTuned
+notifyTuned()} or, if your app could not tune to the proper channel, call
+{@link android.media.tv.TvInputService.RecordingSession#notifyError
+notifyError()}.</p>
+
+<p>The system next invokes the
+{@link android.media.tv.TvInputService.RecordingSession#onStartRecording
+RecordingSession.onStartRecording()} callback. Your app must start recording
+immediately. When the system invokes this callback, it may provide a URI
+that contains information about the program that is about to be recorded.
+When the recording is done, you'll copy this data to the
+{@link android.media.tv.TvContract.RecordedPrograms RecordedPrograms}
+data table.</p>
+
+<p>Finally, the system calls
+{@link android.media.tv.TvInputService.RecordingSession#onStopRecording
+RecordingSession.onStopRecording()}. At this point, your app must stop
+recording immediately. You also need to create an entry in the
+{@link android.media.tv.TvContract.RecordedPrograms RecordedPrograms}
+table. This entry should include the recorded session data URI in the
+{@link android.media.tv.TvContract.RecordedPrograms#COLUMN_RECORDING_DATA_URI
+RecordedPrograms.COLUMN_RECORDING_DATA_URI} column, and any program
+information that the system provided in the initial call to
+{@link android.media.tv.TvInputService.RecordingSession#onStartRecording
+onStartRecording()}.</p>
+
+<p>For more details on how to access the
+{@link android.media.tv.TvContract.RecordedPrograms RecordedPrograms} table
+see <a href="#sessions">Managing Recorded Sessions</a>.</p>
+
+<h2 id="errors">Handling Recording Errors</h2>
+
+<p>If an error occurs during recording, resulting in unusable recorded data,
+notify the system by calling
+{@link android.media.tv.TvInputService.RecordingSession#notifyError
+notifyError()}. Similarly, you can call
+{@link android.media.tv.TvInputService.RecordingSession#notifyError
+notifyError()} after a recording session is created to let the system know
+that your app can no longer record sessions.</p>
+
+<p>If an error occurs during recording, but you'd like to provide a
+partial recording to users for playback, call
+{@link android.media.tv.TvInputService.RecordingSession#notifyRecordingStopped
+notifyRecordingStopped()} to enable the system to
+use the partial session.</p>
+
+<h2 id="sessions">Managing Recorded Sessions</h2>
+
+<p>The system maintains information for all recorded sessions from all
+recording-capable channel apps in the
+{@link android.media.tv.TvContract.RecordedPrograms RecordedPrograms}
+content provider table. This information is accessible via the
+{@link android.media.tv.TvContract.RecordedPrograms RecordedPrograms}
+content recording URIs. Use content provider APIs to
+read, add, and delete entries from this table.</p>
+
+<p>For more information on working with content provider data see
+<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">
+Content Provider Basics</a>.</p>
+
+<h2 id="best">Best Practices</h2>
+
+<p>TV devices may have limited storage, so use your best judgment when
+allocating storage to save recorded sessions. Use
+{@link android.media.tv.TvRecordingClient.RecordingCallback#onError
+RecordingCallback.onError(RECORDING_ERROR_INSUFFICIENT_SPACE)} when
+there isn't enough space to save a recorded session.</p>
+
+<p>When the user initiates recording, you should start recording data as soon
+as possible. To facilitate this, complete any up-front time-consuming tasks,
+like accessing and allocating storage space, when the system invokes the
+{@link android.media.tv.TvInputService#onCreateRecordingSession
+onCreateRecordingSession()} callback. Doing so lets you start
+recording immediately when the
+{@link android.media.tv.TvInputService.RecordingSession#onStartRecording
+onStartRecording()} callback fires.</p>
diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp
index 6dbcd3f..9a242fb 100644
--- a/libs/hwui/RecordingCanvas.cpp
+++ b/libs/hwui/RecordingCanvas.cpp
@@ -268,7 +268,7 @@
 
 // Geometry
 void RecordingCanvas::drawPoints(const float* points, int floatCount, const SkPaint& paint) {
-    if (floatCount < 2) return;
+    if (CC_UNLIKELY(floatCount < 2 || PaintUtils::paintWillNotDraw(paint))) return;
     floatCount &= ~0x1; // round down to nearest two
 
     addOp(alloc().create_trivial<PointsOp>(
@@ -279,7 +279,7 @@
 }
 
 void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) {
-    if (floatCount < 4) return;
+    if (CC_UNLIKELY(floatCount < 4 || PaintUtils::paintWillNotDraw(paint))) return;
     floatCount &= ~0x3; // round down to nearest four
 
     addOp(alloc().create_trivial<LinesOp>(
@@ -290,6 +290,8 @@
 }
 
 void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) {
+    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
     addOp(alloc().create_trivial<RectOp>(
             Rect(left, top, right, bottom),
             *(mState.currentSnapshot()->transform),
@@ -331,6 +333,8 @@
 }
 
 void RecordingCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) {
+    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
     if (paint.getStyle() == SkPaint::kFill_Style
             && (!paint.isAntiAlias() || mState.currentTransform()->isSimple())) {
         int count = 0;
@@ -355,8 +359,11 @@
         }
     }
 }
+
 void RecordingCanvas::drawRoundRect(float left, float top, float right, float bottom,
             float rx, float ry, const SkPaint& paint) {
+    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
     if (CC_LIKELY(MathUtils::isPositive(rx) || MathUtils::isPositive(ry))) {
         addOp(alloc().create_trivial<RoundRectOp>(
                 Rect(left, top, right, bottom),
@@ -391,7 +398,8 @@
 
 void RecordingCanvas::drawCircle(float x, float y, float radius, const SkPaint& paint) {
     // TODO: move to Canvas.h
-    if (radius <= 0) return;
+    if (CC_UNLIKELY(radius <= 0 || PaintUtils::paintWillNotDraw(paint))) return;
+
     drawOval(x - radius, y - radius, x + radius, y + radius, paint);
 }
 
@@ -411,6 +419,8 @@
 }
 
 void RecordingCanvas::drawOval(float left, float top, float right, float bottom, const SkPaint& paint) {
+    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
     addOp(alloc().create_trivial<OvalOp>(
             Rect(left, top, right, bottom),
             *(mState.currentSnapshot()->transform),
@@ -420,6 +430,8 @@
 
 void RecordingCanvas::drawArc(float left, float top, float right, float bottom,
         float startAngle, float sweepAngle, bool useCenter, const SkPaint& paint) {
+    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
     if (fabs(sweepAngle) >= 360.0f) {
         drawOval(left, top, right, bottom, paint);
     } else {
@@ -433,6 +445,8 @@
 }
 
 void RecordingCanvas::drawPath(const SkPath& path, const SkPaint& paint) {
+    if (CC_UNLIKELY(PaintUtils::paintWillNotDraw(paint))) return;
+
     addOp(alloc().create_trivial<PathOp>(
             Rect(path.getBounds()),
             *(mState.currentSnapshot()->transform),
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index e5fc556..392929f 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -1459,7 +1459,8 @@
 
 static void drawOrderedRect(RecordingCanvas* canvas, uint8_t expectedDrawOrder) {
     SkPaint paint;
-    paint.setColor(SkColorSetARGB(256, 0, 0, expectedDrawOrder)); // order put in blue channel
+    // order put in blue channel, transparent so overlapped content doesn't get rejected
+    paint.setColor(SkColorSetARGB(1, 0, 0, expectedDrawOrder));
     canvas->drawRect(0, 0, 100, 100, paint);
 }
 static void drawOrderedNode(RecordingCanvas* canvas, uint8_t expectedDrawOrder, float z) {
diff --git a/libs/hwui/tests/unit/RecordingCanvasTests.cpp b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
index d75ceb2..efc6b4c 100644
--- a/libs/hwui/tests/unit/RecordingCanvasTests.cpp
+++ b/libs/hwui/tests/unit/RecordingCanvasTests.cpp
@@ -81,6 +81,27 @@
     ASSERT_EQ(0u, dl->getOps().size()) << "Must be zero ops. Rect should be rejected.";
 }
 
+TEST(RecordingCanvas, emptyPaintRejection) {
+    auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
+        SkPaint emptyPaint;
+        emptyPaint.setColor(Color::Transparent);
+
+        float points[] = {0, 0, 200, 200};
+        canvas.drawPoints(points, 4, emptyPaint);
+        canvas.drawLines(points, 4, emptyPaint);
+        canvas.drawRect(0, 0, 200, 200, emptyPaint);
+        canvas.drawRegion(SkRegion(SkIRect::MakeWH(200, 200)), emptyPaint);
+        canvas.drawRoundRect(0, 0, 200, 200, 10, 10, emptyPaint);
+        canvas.drawCircle(100, 100, 100, emptyPaint);
+        canvas.drawOval(0, 0, 200, 200, emptyPaint);
+        canvas.drawArc(0, 0, 200, 200, 0, 360, true, emptyPaint);
+        SkPath path;
+        path.addRect(0, 0, 200, 200);
+        canvas.drawPath(path, emptyPaint);
+    });
+    EXPECT_EQ(0u, dl->getOps().size()) << "Op should be rejected";
+}
+
 TEST(RecordingCanvas, drawArc) {
     auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) {
         canvas.drawArc(0, 0, 200, 200, 0, 180, true, SkPaint());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 716e297..325a937 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1966,10 +1966,6 @@
         PackageManagerService m = new PackageManagerService(context, installer,
                 factoryTest, onlyCore);
         m.enableSystemUserPackages();
-        // Disable any carrier apps. We do this very early in boot to prevent the apps from being
-        // disabled after already being started.
-        CarrierAppUtils.disableCarrierAppsUntilPrivileged(context.getOpPackageName(), m,
-                UserHandle.USER_SYSTEM);
         ServiceManager.addService("package", m);
         return m;
     }
@@ -17941,6 +17937,11 @@
     public void systemReady() {
         mSystemReady = true;
 
+        // Disable any carrier apps. We do this very early in boot to prevent the apps from being
+        // disabled after already being started.
+        CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
+                mContext.getContentResolver(), UserHandle.USER_SYSTEM);
+
         // Read the compatibilty setting when the system is ready.
         boolean compatibilityModeEnabled = android.provider.Settings.Global.getInt(
                 mContext.getContentResolver(),
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index 1d19637..df9242d 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -49,7 +49,6 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.os.SomeArgs;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.FgThread;
 
@@ -321,7 +320,6 @@
         private boolean mConnected;
         private boolean mHostConnected;
         private boolean mSourcePower;
-        private boolean mSinkPower;
         private boolean mConfigured;
         private boolean mUsbDataUnlocked;
         private String mCurrentFunctions;
@@ -403,19 +401,7 @@
         public void updateHostState(UsbPort port, UsbPortStatus status) {
             boolean hostConnected = status.getCurrentDataRole() == UsbPort.DATA_ROLE_HOST;
             boolean sourcePower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SOURCE;
-            boolean sinkPower = status.getCurrentPowerRole() == UsbPort.POWER_ROLE_SINK;
-
-            if (DEBUG) {
-                Slog.i(TAG, "updateHostState " + port + ": dataRole=" + status.getCurrentDataRole() +
-                        ", powerRole=" + status.getCurrentPowerRole());
-            }
-
-            SomeArgs args = SomeArgs.obtain();
-            args.argi1 = hostConnected ? 1 :0;
-            args.argi2 = sourcePower ? 1 :0;
-            args.argi3 = sinkPower ? 1 :0;
-
-            obtainMessage(MSG_UPDATE_HOST_STATE, args).sendToTarget();
+            obtainMessage(MSG_UPDATE_HOST_STATE, hostConnected ? 1 :0, sourcePower ? 1 :0).sendToTarget();
         }
 
         private boolean waitForState(String state) {
@@ -732,11 +718,8 @@
                     }
                     break;
                 case MSG_UPDATE_HOST_STATE:
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    mHostConnected = (args.argi1 == 1);
-                    mSourcePower = (args.argi2 == 1);
-                    mSinkPower = (args.argi3 == 1);
-                    args.recycle();
+                    mHostConnected = (msg.arg1 == 1);
+                    mSourcePower = (msg.arg2 == 1);
                     updateUsbNotification();
                     if (mBootCompleted) {
                         updateUsbStateBroadcastIfNeeded();
@@ -826,8 +809,6 @@
                 }
             } else if (mSourcePower) {
                 id = com.android.internal.R.string.usb_supplying_notification_title;
-            } else if (mSinkPower) {
-                id = com.android.internal.R.string.usb_charging_notification_title;
             }
             if (id != mUsbNotificationId) {
                 // clear notification if title needs changing
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index ee055f4..4da5ff2 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -514,67 +514,87 @@
      * @return A human readable string representation.
      */
     public static String capabilitiesToString(int capabilities) {
+        return capabilitiesToStringInternal(capabilities, true /* isLong */);
+    }
+
+    /**
+     * Renders a set of capability bits ({@code CAPABILITY_*}) as a *short* human readable
+     * string.
+     *
+     * @param capabilities A capability bit field.
+     * @return A human readable string representation.
+     * @hide
+     */
+    public static String capabilitiesToStringShort(int capabilities) {
+        return capabilitiesToStringInternal(capabilities, false /* isLong */);
+    }
+
+    private static String capabilitiesToStringInternal(int capabilities, boolean isLong) {
         StringBuilder builder = new StringBuilder();
-        builder.append("[Capabilities:");
+        builder.append("[");
+        if (isLong) {
+            builder.append("Capabilities:");
+        }
+
         if (can(capabilities, CAPABILITY_HOLD)) {
-            builder.append(" CAPABILITY_HOLD");
+            builder.append(isLong ? " CAPABILITY_HOLD" : " hld");
         }
         if (can(capabilities, CAPABILITY_SUPPORT_HOLD)) {
-            builder.append(" CAPABILITY_SUPPORT_HOLD");
+            builder.append(isLong ? " CAPABILITY_SUPPORT_HOLD" : " sup_hld");
         }
         if (can(capabilities, CAPABILITY_MERGE_CONFERENCE)) {
-            builder.append(" CAPABILITY_MERGE_CONFERENCE");
+            builder.append(isLong ? " CAPABILITY_MERGE_CONFERENCE" : " mrg_cnf");
         }
         if (can(capabilities, CAPABILITY_SWAP_CONFERENCE)) {
-            builder.append(" CAPABILITY_SWAP_CONFERENCE");
+            builder.append(isLong ? " CAPABILITY_SWAP_CONFERENCE" : " swp_cnf");
         }
         if (can(capabilities, CAPABILITY_RESPOND_VIA_TEXT)) {
-            builder.append(" CAPABILITY_RESPOND_VIA_TEXT");
+            builder.append(isLong ? " CAPABILITY_RESPOND_VIA_TEXT" : " txt");
         }
         if (can(capabilities, CAPABILITY_MUTE)) {
-            builder.append(" CAPABILITY_MUTE");
+            builder.append(isLong ? " CAPABILITY_MUTE" : " mut");
         }
         if (can(capabilities, CAPABILITY_MANAGE_CONFERENCE)) {
-            builder.append(" CAPABILITY_MANAGE_CONFERENCE");
+            builder.append(isLong ? " CAPABILITY_MANAGE_CONFERENCE" : " mng_cnf");
         }
         if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_RX)) {
-            builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_RX");
+            builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_LOCAL_RX" : " VTlrx");
         }
         if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_TX)) {
-            builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_TX");
+            builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_LOCAL_TX" : " VTltx");
         }
         if (can(capabilities, CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) {
-            builder.append(" CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL");
+            builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL" : " VTlbi");
         }
         if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_RX)) {
-            builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_RX");
+            builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_REMOTE_RX" : " VTrrx");
         }
         if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_TX)) {
-            builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_TX");
+            builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_REMOTE_TX" : " VTrtx");
         }
         if (can(capabilities, CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)) {
-            builder.append(" CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL");
+            builder.append(isLong ? " CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL" : " VTrbi");
         }
         if (can(capabilities, CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO)) {
-            builder.append(" CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO");
+            builder.append(isLong ? " CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO" : " !v2a");
         }
         if (can(capabilities, CAPABILITY_SPEED_UP_MT_AUDIO)) {
-            builder.append(" CAPABILITY_SPEED_UP_MT_AUDIO");
+            builder.append(isLong ? " CAPABILITY_SPEED_UP_MT_AUDIO" : " spd_aud");
         }
         if (can(capabilities, CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
-            builder.append(" CAPABILITY_CAN_UPGRADE_TO_VIDEO");
+            builder.append(isLong ? " CAPABILITY_CAN_UPGRADE_TO_VIDEO" : " a2v");
         }
         if (can(capabilities, CAPABILITY_CAN_PAUSE_VIDEO)) {
-            builder.append(" CAPABILITY_CAN_PAUSE_VIDEO");
+            builder.append(isLong ? " CAPABILITY_CAN_PAUSE_VIDEO" : " paus_VT");
         }
         if (can(capabilities, CAPABILITY_CONFERENCE_HAS_NO_CHILDREN)) {
-            builder.append(" CAPABILITY_SINGLE_PARTY_CONFERENCE");
+            builder.append(isLong ? " CAPABILITY_SINGLE_PARTY_CONFERENCE" : " 1p_cnf");
         }
         if (can(capabilities, CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION)) {
-            builder.append(" CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION");
+            builder.append(isLong ? " CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION" : " rsp_by_con");
         }
         if (can(capabilities, CAPABILITY_CAN_PULL_CALL)) {
-            builder.append(" CAPABILITY_CAN_PULL_CALL");
+            builder.append(isLong ? " CAPABILITY_CAN_PULL_CALL" : " pull");
         }
 
         builder.append("]");
@@ -588,31 +608,49 @@
      * @return A human readable string representation.
      */
     public static String propertiesToString(int properties) {
+        return propertiesToStringInternal(properties, true /* isLong */);
+    }
+
+    /**
+     * Renders a set of property bits ({@code PROPERTY_*}) as a *short* human readable string.
+     *
+     * @param properties A property bit field.
+     * @return A human readable string representation.
+     * @hide
+     */
+    public static String propertiesToStringShort(int properties) {
+        return propertiesToStringInternal(properties, false /* isLong */);
+    }
+
+    private static String propertiesToStringInternal(int properties, boolean isLong) {
         StringBuilder builder = new StringBuilder();
-        builder.append("[Properties:");
+        builder.append("[");
+        if (isLong) {
+            builder.append("Properties:");
+        }
 
         if (can(properties, PROPERTY_SHOW_CALLBACK_NUMBER)) {
-            builder.append(" PROPERTY_SHOW_CALLBACK_NUMBER");
+            builder.append(isLong ? " PROPERTY_SHOW_CALLBACK_NUMBER" : " clbk");
         }
 
         if (can(properties, PROPERTY_HIGH_DEF_AUDIO)) {
-            builder.append(" PROPERTY_HIGH_DEF_AUDIO");
+            builder.append(isLong ? " PROPERTY_HIGH_DEF_AUDIO" : " HD");
         }
 
         if (can(properties, PROPERTY_WIFI)) {
-            builder.append(" PROPERTY_WIFI");
+            builder.append(isLong ? " PROPERTY_WIFI" : " wifi");
         }
 
         if (can(properties, PROPERTY_GENERIC_CONFERENCE)) {
-            builder.append(" PROPERTY_GENERIC_CONFERENCE");
+            builder.append(isLong ? " PROPERTY_GENERIC_CONFERENCE" : " gen_conf");
         }
 
         if (can(properties, PROPERTY_IS_EXTERNAL_CALL)) {
-            builder.append(" PROPERTY_IS_EXTERNAL_CALL");
+            builder.append(isLong ? " PROPERTY_IS_EXTERNAL_CALL" : " xtrnl");
         }
 
         if (can(properties, PROPERTY_HAS_CDMA_VOICE_PRIVACY)) {
-            builder.append(" PROPERTY_HAS_CDMA_VOICE_PRIVACY");
+            builder.append(isLong ? " PROPERTY_HAS_CDMA_VOICE_PRIVACY" : " priv");
         }
 
         builder.append("]");
diff --git a/telephony/java/com/android/internal/telephony/CarrierAppUtils.java b/telephony/java/com/android/internal/telephony/CarrierAppUtils.java
index ca7354f..8b81b0d 100644
--- a/telephony/java/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/java/com/android/internal/telephony/CarrierAppUtils.java
@@ -17,18 +17,23 @@
 package com.android.internal.telephony;
 
 import android.annotation.Nullable;
+import android.content.ContentResolver;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.os.RemoteException;
+import android.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemConfig;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Utilities for handling carrier applications.
@@ -53,6 +58,11 @@
      * in the default state (e.g. not explicitly DISABLED/DISABLED_BY_USER/ENABLED), or we enable if
      * the app is carrier privileged and in either the default state or DISABLED_UNTIL_USED.
      *
+     * In addition, there is a list of carrier-associated applications in
+     * {@link SystemConfig#getDisabledUntilUsedPreinstalledCarrierAssociatedApps}. Each app in this
+     * list is associated with a carrier app. When the given carrier app is enabled/disabled per the
+     * above, the associated applications are enabled/disabled to match.
+     *
      * When enabling a carrier app we also grant it default permissions.
      *
      * This method is idempotent and is safe to be called at any time; it should be called once at
@@ -60,19 +70,24 @@
      * privileged apps may have changed.
      */
     public synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage,
-            IPackageManager packageManager, TelephonyManager telephonyManager, int userId) {
+            IPackageManager packageManager, TelephonyManager telephonyManager,
+            ContentResolver contentResolver, int userId) {
         if (DEBUG) {
             Slog.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
+        SystemConfig config = SystemConfig.getInstance();
         String[] systemCarrierAppsDisabledUntilUsed = Resources.getSystem().getStringArray(
                 com.android.internal.R.array.config_disabledUntilUsedPreinstalledCarrierApps);
-        disableCarrierAppsUntilPrivileged(callingPackage, packageManager, telephonyManager, userId,
-                systemCarrierAppsDisabledUntilUsed);
+        ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
+                config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+        disableCarrierAppsUntilPrivileged(callingPackage, packageManager, telephonyManager,
+                contentResolver, userId, systemCarrierAppsDisabledUntilUsed,
+                systemCarrierAssociatedAppsDisabledUntilUsed);
     }
 
     /**
      * Like {@link #disableCarrierAppsUntilPrivileged(String, IPackageManager, TelephonyManager,
-     * int)}, but assumes that no carrier apps have carrier privileges.
+     * ContentResolver, int)}, but assumes that no carrier apps have carrier privileges.
      *
      * This prevents a potential race condition on first boot - since the app's default state is
      * enabled, we will initially disable it when the telephony stack is first initialized as it has
@@ -82,29 +97,43 @@
      * Manager can kill it, and this can lead to crashes as the app is in an unexpected state.
      */
     public synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage,
-            IPackageManager packageManager, int userId) {
+            IPackageManager packageManager, ContentResolver contentResolver, int userId) {
         if (DEBUG) {
             Slog.d(TAG, "disableCarrierAppsUntilPrivileged");
         }
+        SystemConfig config = SystemConfig.getInstance();
         String[] systemCarrierAppsDisabledUntilUsed = Resources.getSystem().getStringArray(
                 com.android.internal.R.array.config_disabledUntilUsedPreinstalledCarrierApps);
+        ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
+                config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
         disableCarrierAppsUntilPrivileged(callingPackage, packageManager,
-                null /* telephonyManager */, userId, systemCarrierAppsDisabledUntilUsed);
+                null /* telephonyManager */, contentResolver, userId,
+                systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed);
     }
 
     // Must be public b/c framework unit tests can't access package-private methods.
     @VisibleForTesting
     public static void disableCarrierAppsUntilPrivileged(String callingPackage,
-            IPackageManager packageManager, @Nullable TelephonyManager telephonyManager, int userId,
-            String[] systemCarrierAppsDisabledUntilUsed) {
+            IPackageManager packageManager, @Nullable TelephonyManager telephonyManager,
+            ContentResolver contentResolver, int userId,
+            String[] systemCarrierAppsDisabledUntilUsed,
+            ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) {
         List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(packageManager,
                 userId, systemCarrierAppsDisabledUntilUsed);
         if (candidates == null || candidates.isEmpty()) {
             return;
         }
 
+        Map<String, List<ApplicationInfo>> associatedApps = getDefaultCarrierAssociatedAppsHelper(
+                packageManager,
+                userId,
+                systemCarrierAssociatedAppsDisabledUntilUsed);
+
         List<String> enabledCarrierPackages = new ArrayList<>();
 
+        boolean hasRunOnce = Settings.Secure.getIntForUser(
+                contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1;
+
         try {
             for (ApplicationInfo ai : candidates) {
                 String packageName = ai.packageName;
@@ -112,33 +141,92 @@
                         telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) ==
                                 TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
 
-                // Only update enabled state for the app on /system. Once it has been updated we
-                // shouldn't touch it.
-                if (!ai.isUpdatedSystemApp()) {
-                    if (hasPrivileges
-                            && (ai.enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+                if (hasPrivileges) {
+                    // Only update enabled state for the app on /system. Once it has been
+                    // updated we shouldn't touch it.
+                    if (!ai.isUpdatedSystemApp()
+                            && (ai.enabledSetting ==
+                            PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                             || ai.enabledSetting ==
                             PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED)) {
                         Slog.i(TAG, "Update state(" + packageName + "): ENABLED for user "
                                 + userId);
-                        packageManager.setApplicationEnabledSetting(packageName,
+                        packageManager.setApplicationEnabledSetting(
+                                packageName,
                                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
-                                PackageManager.DONT_KILL_APP, userId, callingPackage);
-                    } else if (!hasPrivileges
+                                PackageManager.DONT_KILL_APP,
+                                userId,
+                                callingPackage);
+                    }
+
+                    // Also enable any associated apps for this carrier app.
+                    List<ApplicationInfo> associatedAppList = associatedApps.get(packageName);
+                    if (associatedAppList != null) {
+                        for (ApplicationInfo associatedApp : associatedAppList) {
+                            if (associatedApp.enabledSetting ==
+                                    PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+                                    || associatedApp.enabledSetting ==
+                                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
+                                Slog.i(TAG, "Update associated state(" + associatedApp.packageName
+                                        + "): ENABLED for user " + userId);
+                                packageManager.setApplicationEnabledSetting(
+                                        associatedApp.packageName,
+                                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                                        PackageManager.DONT_KILL_APP,
+                                        userId,
+                                        callingPackage);
+                            }
+                        }
+                    }
+
+                    // Always re-grant default permissions to carrier apps w/ privileges.
+                    enabledCarrierPackages.add(ai.packageName);
+                } else {  // No carrier privileges
+                    // Only update enabled state for the app on /system. Once it has been
+                    // updated we shouldn't touch it.
+                    if (!ai.isUpdatedSystemApp()
                             && ai.enabledSetting ==
                             PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
                         Slog.i(TAG, "Update state(" + packageName
                                 + "): DISABLED_UNTIL_USED for user " + userId);
-                        packageManager.setApplicationEnabledSetting(packageName,
-                                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0,
-                                userId, callingPackage);
+                        packageManager.setApplicationEnabledSetting(
+                                packageName,
+                                PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+                                0,
+                                userId,
+                                callingPackage);
+                    }
+
+                    // Also disable any associated apps for this carrier app if this is the first
+                    // run. We avoid doing this a second time because it is brittle to rely on the
+                    // distinction between "default" and "enabled".
+                    if (!hasRunOnce) {
+                        List<ApplicationInfo> associatedAppList = associatedApps.get(packageName);
+                        if (associatedAppList != null) {
+                            for (ApplicationInfo associatedApp : associatedAppList) {
+                                if (associatedApp.enabledSetting
+                                        == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) {
+                                    Slog.i(TAG,
+                                            "Update associated state(" + associatedApp.packageName
+                                                    + "): DISABLED_UNTIL_USED for user " + userId);
+                                    packageManager.setApplicationEnabledSetting(
+                                            associatedApp.packageName,
+                                            PackageManager
+                                                    .COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED,
+                                            0,
+                                            userId,
+                                            callingPackage);
+                                }
+                            }
+                        }
                     }
                 }
+            }
 
-                // Always re-grant default permissions to carrier apps w/ privileges.
-                if (hasPrivileges) {
-                    enabledCarrierPackages.add(ai.packageName);
-                }
+            // Mark the execution so we do not disable apps again.
+            if (!hasRunOnce) {
+                Settings.Secure.putIntForUser(
+                        contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId);
             }
 
             if (!enabledCarrierPackages.isEmpty()) {
@@ -190,8 +278,8 @@
      *
      * These are the apps subject to the hiding/showing logic in
      * {@link CarrierAppUtils#disableCarrierAppsUntilPrivileged(String, IPackageManager,
-     * TelephonyManager, int)}, as well as the apps which should have default permissions granted,
-     * when a matching SIM is inserted.
+     * TelephonyManager, ContentResolver, int)}, as well as the apps which should have default
+     * permissions granted, when a matching SIM is inserted.
      *
      * Whether or not the app is actually considered a default app depends on whether the app has
      * carrier privileges as determined by the SIMs in the device.
@@ -205,30 +293,68 @@
     }
 
     private static List<ApplicationInfo> getDefaultCarrierAppCandidatesHelper(
-            IPackageManager packageManager, int userId,
+            IPackageManager packageManager,
+            int userId,
             String[] systemCarrierAppsDisabledUntilUsed) {
         if (systemCarrierAppsDisabledUntilUsed == null
                 || systemCarrierAppsDisabledUntilUsed.length == 0) {
             return null;
         }
-        List<ApplicationInfo> apps = null;
-        try {
-            apps = new ArrayList<>(systemCarrierAppsDisabledUntilUsed.length);
-            for (String packageName : systemCarrierAppsDisabledUntilUsed) {
-                ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
-                        PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, userId);
-                if (ai == null) {
-                    // No app found for packageName
-                    continue;
-                }
-                if (!ai.isSystemApp()) {
-                    continue;
-                }
+        List<ApplicationInfo> apps = new ArrayList<>(systemCarrierAppsDisabledUntilUsed.length);
+        for (int i = 0; i < systemCarrierAppsDisabledUntilUsed.length; i++) {
+            String packageName = systemCarrierAppsDisabledUntilUsed[i];
+            ApplicationInfo ai =
+                    getApplicationInfoIfSystemApp(packageManager, userId, packageName);
+            if (ai != null) {
                 apps.add(ai);
             }
+        }
+        return apps;
+    }
+
+    private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper(
+            IPackageManager packageManager,
+            int userId,
+            ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) {
+        int size = systemCarrierAssociatedAppsDisabledUntilUsed.size();
+        Map<String, List<ApplicationInfo>> associatedApps = new ArrayMap<>(size);
+        for (int i = 0; i < size; i++) {
+            String carrierAppPackage = systemCarrierAssociatedAppsDisabledUntilUsed.keyAt(i);
+            List<String> associatedAppPackages =
+                    systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i);
+            for (int j = 0; j < associatedAppPackages.size(); j++) {
+                ApplicationInfo ai =
+                        getApplicationInfoIfSystemApp(
+                                packageManager, userId, associatedAppPackages.get(j));
+                // Only update enabled state for the app on /system. Once it has been updated we
+                // shouldn't touch it.
+                if (ai != null && !ai.isUpdatedSystemApp()) {
+                    List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage);
+                    if (appList == null) {
+                        appList = new ArrayList<>();
+                        associatedApps.put(carrierAppPackage, appList);
+                    }
+                    appList.add(ai);
+                }
+            }
+        }
+        return associatedApps;
+    }
+
+    @Nullable
+    private static ApplicationInfo getApplicationInfoIfSystemApp(
+            IPackageManager packageManager,
+            int userId,
+            String packageName) {
+        try {
+            ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
+                    PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS, userId);
+            if (ai != null && ai.isSystemApp()) {
+                return ai;
+            }
         } catch (RemoteException e) {
             Slog.w(TAG, "Could not reach PackageManager", e);
         }
-        return apps;
+        return null;
     }
 }
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 00d8aae..a74b5aa 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -21,6 +21,18 @@
 
 namespace aapt {
 
+// DO NOT UPDATE, this is more of a marketing version.
+static const char* sMajorVersion = "2";
+
+// Update minor version whenever a feature or flag is added.
+static const char* sMinorVersion = "0";
+
+int printVersion() {
+    std::cerr << "Android Asset Packaging Tool (aapt) "
+            << sMajorVersion << "." << sMinorVersion << std::endl;
+    return 0;
+}
+
 extern int compile(const std::vector<StringPiece>& args);
 extern int link(const std::vector<StringPiece>& args);
 extern int dump(const std::vector<StringPiece>& args);
@@ -47,12 +59,14 @@
             return aapt::dump(args);
         } else if (command == "diff") {
             return aapt::diff(args);
+        } else if (command == "version") {
+            return aapt::printVersion();
         }
         std::cerr << "unknown command '" << command << "'\n";
     } else {
         std::cerr << "no command specified\n";
     }
 
-    std::cerr << "\nusage: aapt2 [compile|link|dump|diff] ..." << std::endl;
+    std::cerr << "\nusage: aapt2 [compile|link|dump|diff|version] ..." << std::endl;
     return 1;
 }
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 22d75a2..0ba0345 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -19,9 +19,10 @@
 
 #include "ConfigDescription.h"
 #include "Source.h"
-
 #include "util/StringPiece.h"
 
+#include <utils/JenkinsHash.h>
+
 #include <iomanip>
 #include <limits>
 #include <sstream>
@@ -353,4 +354,18 @@
 
 } // namespace aapt
 
+namespace std {
+
+template <> struct hash<aapt::ResourceName> {
+    size_t operator()(const aapt::ResourceName& name) const {
+        android::hash_t h = 0;
+        h = android::JenkinsHashMix(h, hash<string>()(name.package));
+        h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type));
+        h = android::JenkinsHashMix(h, hash<string>()(name.entry));
+        return static_cast<size_t>(h);
+    }
+};
+
+} // namespace std
+
 #endif // AAPT_RESOURCE_H
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 45d3db9..a144c6a 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -589,17 +589,14 @@
 
     outResource->name.type = *parsedType;
 
-    if (Maybe<StringPiece> maybeId = xml::findNonEmptyAttribute(parser, "id")) {
-        android::Res_value val;
-        std::u16string idStr16 = util::utf8ToUtf16(maybeId.value());
-        bool result = android::ResTable::stringToInt(idStr16.data(), idStr16.size(), &val);
-        ResourceId resourceId(val.data);
-        if (!result || !resourceId.isValid()) {
+    if (Maybe<StringPiece> maybeIdStr = xml::findNonEmptyAttribute(parser, "id")) {
+        Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(maybeIdStr.value());
+        if (!maybeId) {
             mDiag->error(DiagMessage(outResource->source)
                          << "invalid resource ID '" << maybeId.value() << "' in <public>");
             return false;
         }
-        outResource->id = resourceId;
+        outResource->id = maybeId.value();
     }
 
     if (*parsedType == ResourceType::kId) {
@@ -626,23 +623,22 @@
         return false;
     }
 
-    Maybe<StringPiece> maybeId = xml::findNonEmptyAttribute(parser, "first-id");
-    if (!maybeId) {
+    Maybe<StringPiece> maybeIdStr = xml::findNonEmptyAttribute(parser, "first-id");
+    if (!maybeIdStr) {
         mDiag->error(DiagMessage(outResource->source)
                      << "<public-group> must have a 'first-id' attribute");
         return false;
     }
 
-    android::Res_value val;
-    std::u16string idStr16 = util::utf8ToUtf16(maybeId.value());
-    bool result = android::ResTable::stringToInt(idStr16.data(), idStr16.size(), &val);
-    ResourceId nextId(val.data);
-    if (!result || !nextId.isValid()) {
+    Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(maybeIdStr.value());
+    if (!maybeId) {
         mDiag->error(DiagMessage(outResource->source)
-                     << "invalid resource ID '" << maybeId.value() << "' in <public-group>");
+                     << "invalid resource ID '" << maybeIdStr.value() << "' in <public-group>");
         return false;
     }
 
+    ResourceId nextId = maybeId.value();
+
     std::string comment;
     bool error = false;
     const size_t depth = parser->getDepth();
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index b456c04..3d03a88 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -68,7 +68,7 @@
     std::string input = "<string name=\"foo\">   \"  hey there \" </string>";
     ASSERT_TRUE(testParse(input));
 
-    String* str = test::getValue<String>(&mTable, "@string/foo");
+    String* str = test::getValue<String>(&mTable, "string/foo");
     ASSERT_NE(nullptr, str);
     EXPECT_EQ(std::string("  hey there "), *str->value);
 }
@@ -77,7 +77,7 @@
     std::string input = "<string name=\"foo\">\\?123</string>";
     ASSERT_TRUE(testParse(input));
 
-    String* str = test::getValue<String>(&mTable, "@string/foo");
+    String* str = test::getValue<String>(&mTable, "string/foo");
     ASSERT_NE(nullptr, str);
     EXPECT_EQ(std::string("?123"), *str->value);
 }
@@ -96,7 +96,7 @@
                         "  There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>";
     ASSERT_TRUE(testParse(input));
 
-    String* str = test::getValue<String>(&mTable, "@string/foo");
+    String* str = test::getValue<String>(&mTable, "string/foo");
     ASSERT_NE(nullptr, str);
     EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value));
 }
@@ -109,7 +109,7 @@
     // a non-existing value, and this causes problems in styles when trying to resolve
     // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE
     // with a data value of 0.
-    BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, "@integer/foo");
+    BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
     ASSERT_NE(nullptr, integer);
     EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType);
     EXPECT_EQ(0u, integer->value.data);
@@ -119,7 +119,7 @@
     std::string input = "<integer name=\"foo\">@empty</integer>";
     ASSERT_TRUE(testParse(input));
 
-    BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, "@integer/foo");
+    BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
     ASSERT_NE(nullptr, integer);
     EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType);
     EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data);
@@ -130,11 +130,11 @@
                         "<attr name=\"bar\"/>";
     ASSERT_TRUE(testParse(input));
 
-    Attribute* attr = test::getValue<Attribute>(&mTable, "@attr/foo");
+    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
 
-    attr = test::getValue<Attribute>(&mTable, "@attr/bar");
+    attr = test::getValue<Attribute>(&mTable, "attr/bar");
     ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
 }
@@ -150,20 +150,20 @@
         </declare-styleable>)EOF";
     ASSERT_TRUE(testParse(input, watchConfig));
 
-    EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "@attr/foo", watchConfig));
-    EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "@attr/baz", watchConfig));
-    EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>(&mTable, "@styleable/bar", watchConfig));
+    EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/foo", watchConfig));
+    EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/baz", watchConfig));
+    EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>(&mTable, "styleable/bar", watchConfig));
 
-    EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "@attr/foo"));
-    EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "@attr/baz"));
-    EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, "@styleable/bar"));
+    EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/foo"));
+    EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/baz"));
+    EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, "styleable/bar"));
 }
 
 TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
     std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
     ASSERT_TRUE(testParse(input));
 
-    Attribute* attr = test::getValue<Attribute>(&mTable, "@attr/foo");
+    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask);
     EXPECT_EQ(10, attr->minInt);
@@ -182,7 +182,7 @@
                         "<attr name=\"foo\" format=\"string\"/>";
     ASSERT_TRUE(testParse(input));
 
-    Attribute* attr = test::getValue<Attribute>(&mTable, "@attr/foo");
+    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
 }
@@ -196,7 +196,7 @@
                         "</declare-styleable>";
     ASSERT_TRUE(testParse(input));
 
-    Attribute* attr = test::getValue<Attribute>(&mTable, "@attr/foo");
+    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
 }
@@ -209,7 +209,7 @@
                         "</attr>";
     ASSERT_TRUE(testParse(input));
 
-    Attribute* enumAttr = test::getValue<Attribute>(&mTable, "@attr/foo");
+    Attribute* enumAttr = test::getValue<Attribute>(&mTable, "attr/foo");
     ASSERT_NE(enumAttr, nullptr);
     EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
     ASSERT_EQ(enumAttr->symbols.size(), 3u);
@@ -235,7 +235,7 @@
                         "</attr>";
     ASSERT_TRUE(testParse(input));
 
-    Attribute* flagAttr = test::getValue<Attribute>(&mTable, "@attr/foo");
+    Attribute* flagAttr = test::getValue<Attribute>(&mTable, "attr/foo");
     ASSERT_NE(nullptr, flagAttr);
     EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
     ASSERT_EQ(flagAttr->symbols.size(), 3u);
@@ -275,32 +275,32 @@
                         "</style>";
     ASSERT_TRUE(testParse(input));
 
-    Style* style = test::getValue<Style>(&mTable, "@style/foo");
+    Style* style = test::getValue<Style>(&mTable, "style/foo");
     ASSERT_NE(nullptr, style);
     AAPT_ASSERT_TRUE(style->parent);
     AAPT_ASSERT_TRUE(style->parent.value().name);
-    EXPECT_EQ(test::parseNameOrDie("@style/fu"), style->parent.value().name.value());
+    EXPECT_EQ(test::parseNameOrDie("style/fu"), style->parent.value().name.value());
     ASSERT_EQ(3u, style->entries.size());
 
     AAPT_ASSERT_TRUE(style->entries[0].key.name);
-    EXPECT_EQ(test::parseNameOrDie("@attr/bar"), style->entries[0].key.name.value());
+    EXPECT_EQ(test::parseNameOrDie("attr/bar"), style->entries[0].key.name.value());
 
     AAPT_ASSERT_TRUE(style->entries[1].key.name);
-    EXPECT_EQ(test::parseNameOrDie("@attr/bat"), style->entries[1].key.name.value());
+    EXPECT_EQ(test::parseNameOrDie("attr/bat"), style->entries[1].key.name.value());
 
     AAPT_ASSERT_TRUE(style->entries[2].key.name);
-    EXPECT_EQ(test::parseNameOrDie("@attr/baz"), style->entries[2].key.name.value());
+    EXPECT_EQ(test::parseNameOrDie("attr/baz"), style->entries[2].key.name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
     std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>";
     ASSERT_TRUE(testParse(input));
 
-    Style* style = test::getValue<Style>(&mTable, "@style/foo");
+    Style* style = test::getValue<Style>(&mTable, "style/foo");
     ASSERT_NE(nullptr, style);
     AAPT_ASSERT_TRUE(style->parent);
     AAPT_ASSERT_TRUE(style->parent.value().name);
-    EXPECT_EQ(test::parseNameOrDie("@com.app:style/Theme"), style->parent.value().name.value());
+    EXPECT_EQ(test::parseNameOrDie("com.app:style/Theme"), style->parent.value().name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) {
@@ -308,11 +308,11 @@
                         "       name=\"foo\" parent=\"app:Theme\"/>";
     ASSERT_TRUE(testParse(input));
 
-    Style* style = test::getValue<Style>(&mTable, "@style/foo");
+    Style* style = test::getValue<Style>(&mTable, "style/foo");
     ASSERT_NE(nullptr, style);
     AAPT_ASSERT_TRUE(style->parent);
     AAPT_ASSERT_TRUE(style->parent.value().name);
-    EXPECT_EQ(test::parseNameOrDie("@android:style/Theme"), style->parent.value().name.value());
+    EXPECT_EQ(test::parseNameOrDie("android:style/Theme"), style->parent.value().name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) {
@@ -322,21 +322,21 @@
             "</style>";
     ASSERT_TRUE(testParse(input));
 
-    Style* style = test::getValue<Style>(&mTable, "@style/foo");
+    Style* style = test::getValue<Style>(&mTable, "style/foo");
     ASSERT_NE(nullptr, style);
     ASSERT_EQ(1u, style->entries.size());
-    EXPECT_EQ(test::parseNameOrDie("@android:attr/bar"), style->entries[0].key.name.value());
+    EXPECT_EQ(test::parseNameOrDie("android:attr/bar"), style->entries[0].key.name.value());
 }
 
 TEST_F(ResourceParserTest, ParseStyleWithInferredParent) {
     std::string input = "<style name=\"foo.bar\"/>";
     ASSERT_TRUE(testParse(input));
 
-    Style* style = test::getValue<Style>(&mTable, "@style/foo.bar");
+    Style* style = test::getValue<Style>(&mTable, "style/foo.bar");
     ASSERT_NE(nullptr, style);
     AAPT_ASSERT_TRUE(style->parent);
     AAPT_ASSERT_TRUE(style->parent.value().name);
-    EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie("@style/foo"));
+    EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie("style/foo"));
     EXPECT_TRUE(style->parentInferred);
 }
 
@@ -344,7 +344,7 @@
     std::string input = "<style name=\"foo.bar\" parent=\"\"/>";
     ASSERT_TRUE(testParse(input));
 
-    Style* style = test::getValue<Style>(&mTable, "@style/foo.bar");
+    Style* style = test::getValue<Style>(&mTable, "style/foo.bar");
     ASSERT_NE(nullptr, style);
     AAPT_EXPECT_FALSE(style->parent);
     EXPECT_FALSE(style->parentInferred);
@@ -354,7 +354,7 @@
     std::string input = R"EOF(<style name="foo" parent="*android:style/bar" />)EOF";
     ASSERT_TRUE(testParse(input));
 
-    Style* style = test::getValue<Style>(&mTable, "@style/foo");
+    Style* style = test::getValue<Style>(&mTable, "style/foo");
     ASSERT_NE(nullptr, style);
     AAPT_ASSERT_TRUE(style->parent);
     EXPECT_TRUE(style->parent.value().privateReference);
@@ -364,7 +364,7 @@
     std::string input = "<string name=\"foo\">@+id/bar</string>";
     ASSERT_TRUE(testParse(input));
 
-    Id* id = test::getValue<Id>(&mTable, "@id/bar");
+    Id* id = test::getValue<Id>(&mTable, "id/bar");
     ASSERT_NE(id, nullptr);
 }
 
@@ -379,31 +379,31 @@
     ASSERT_TRUE(testParse(input));
 
     Maybe<ResourceTable::SearchResult> result =
-            mTable.findResource(test::parseNameOrDie("@styleable/foo"));
+            mTable.findResource(test::parseNameOrDie("styleable/foo"));
     AAPT_ASSERT_TRUE(result);
     EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
 
-    Attribute* attr = test::getValue<Attribute>(&mTable, "@attr/bar");
+    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/bar");
     ASSERT_NE(attr, nullptr);
     EXPECT_TRUE(attr->isWeak());
 
-    attr = test::getValue<Attribute>(&mTable, "@attr/bat");
+    attr = test::getValue<Attribute>(&mTable, "attr/bat");
     ASSERT_NE(attr, nullptr);
     EXPECT_TRUE(attr->isWeak());
 
-    attr = test::getValue<Attribute>(&mTable, "@attr/baz");
+    attr = test::getValue<Attribute>(&mTable, "attr/baz");
     ASSERT_NE(attr, nullptr);
     EXPECT_TRUE(attr->isWeak());
     EXPECT_EQ(1u, attr->symbols.size());
 
-    EXPECT_NE(nullptr, test::getValue<Id>(&mTable, "@id/foo"));
+    EXPECT_NE(nullptr, test::getValue<Id>(&mTable, "id/foo"));
 
-    Styleable* styleable = test::getValue<Styleable>(&mTable, "@styleable/foo");
+    Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
     ASSERT_NE(styleable, nullptr);
     ASSERT_EQ(3u, styleable->entries.size());
 
-    EXPECT_EQ(test::parseNameOrDie("@attr/bar"), styleable->entries[0].name.value());
-    EXPECT_EQ(test::parseNameOrDie("@attr/bat"), styleable->entries[1].name.value());
+    EXPECT_EQ(test::parseNameOrDie("attr/bar"), styleable->entries[0].name.value());
+    EXPECT_EQ(test::parseNameOrDie("attr/bat"), styleable->entries[1].name.value());
 }
 
 TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
@@ -412,7 +412,7 @@
                         "  <attr name=\"privAndroid:bat\" />\n"
                         "</declare-styleable>";
     ASSERT_TRUE(testParse(input));
-    Styleable* styleable = test::getValue<Styleable>(&mTable, "@styleable/foo");
+    Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
     ASSERT_NE(nullptr, styleable);
     ASSERT_EQ(2u, styleable->entries.size());
 
@@ -433,7 +433,7 @@
                         "</array>";
     ASSERT_TRUE(testParse(input));
 
-    Array* array = test::getValue<Array>(&mTable, "@array/foo");
+    Array* array = test::getValue<Array>(&mTable, "array/foo");
     ASSERT_NE(array, nullptr);
     ASSERT_EQ(3u, array->items.size());
 
@@ -447,7 +447,7 @@
                         "  <item>\"Werk\"</item>\n"
                         "</string-array>\n";
     ASSERT_TRUE(testParse(input));
-    EXPECT_NE(nullptr, test::getValue<Array>(&mTable, "@array/foo"));
+    EXPECT_NE(nullptr, test::getValue<Array>(&mTable, "array/foo"));
 }
 
 TEST_F(ResourceParserTest, ParsePlural) {
@@ -463,7 +463,7 @@
                         "<string name=\"foo\">Hi</string>";
     ASSERT_TRUE(testParse(input));
 
-    String* value = test::getValue<String>(&mTable, "@string/foo");
+    String* value = test::getValue<String>(&mTable, "string/foo");
     ASSERT_NE(nullptr, value);
     EXPECT_EQ(value->getComment(), "This is a comment");
 }
@@ -475,7 +475,7 @@
 
     ASSERT_TRUE(testParse(input));
 
-    String* value = test::getValue<String>(&mTable, "@string/foo");
+    String* value = test::getValue<String>(&mTable, "string/foo");
     ASSERT_NE(nullptr, value);
     EXPECT_EQ(value->getComment(), "Two");
 }
@@ -489,7 +489,7 @@
 
     ASSERT_TRUE(testParse(input));
 
-    String* value = test::getValue<String>(&mTable, "@string/foo");
+    String* value = test::getValue<String>(&mTable, "string/foo");
     ASSERT_NE(nullptr, value);
     EXPECT_EQ(value->getComment(), "One");
 }
@@ -509,13 +509,13 @@
         </attr>)EOF";
     ASSERT_TRUE(testParse(input));
 
-    Styleable* styleable = test::getValue<Styleable>(&mTable, "@styleable/foo");
+    Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo");
     ASSERT_NE(nullptr, styleable);
     ASSERT_EQ(1u, styleable->entries.size());
 
     EXPECT_EQ(StringPiece("The name of the bar"), styleable->entries.front().getComment());
 
-    Attribute* attr = test::getValue<Attribute>(&mTable, "@attr/foo");
+    Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo");
     ASSERT_NE(nullptr, attr);
     ASSERT_EQ(1u, attr->symbols.size());
 
@@ -530,7 +530,7 @@
     std::string input = "<public type=\"id\" name=\"foo\"/>";
     ASSERT_TRUE(testParse(input));
 
-    Id* id = test::getValue<Id>(&mTable, "@id/foo");
+    Id* id = test::getValue<Id>(&mTable, "id/foo");
     ASSERT_NE(nullptr, id);
 }
 
@@ -545,22 +545,22 @@
     )EOF";
     ASSERT_TRUE(testParse(input));
 
-    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "@string/foo",
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/foo",
                                                                  ConfigDescription::defaultConfig(),
                                                                  "phone"));
-    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "@string/foo",
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/foo",
                                                                  ConfigDescription::defaultConfig(),
                                                                  "no-sdcard"));
-    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "@string/bar",
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bar",
                                                                  ConfigDescription::defaultConfig(),
                                                                  ""));
-    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "@string/baz",
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/baz",
                                                                  ConfigDescription::defaultConfig(),
                                                                  ""));
-    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "@string/bit",
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bit",
                                                                  ConfigDescription::defaultConfig(),
                                                                  "phablet"));
-    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "@string/bot",
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bot",
                                                                  ConfigDescription::defaultConfig(),
                                                                  "default"));
 }
@@ -574,7 +574,7 @@
     ASSERT_TRUE(testParse(input));
 
     Maybe<ResourceTable::SearchResult> result = mTable.findResource(
-            test::parseNameOrDie("@attr/foo"));
+            test::parseNameOrDie("attr/foo"));
     AAPT_ASSERT_TRUE(result);
 
     AAPT_ASSERT_TRUE(result.value().package->id);
@@ -585,7 +585,7 @@
                         result.value().entry->id.value());
     EXPECT_EQ(ResourceId(0x01010040), actualId);
 
-    result = mTable.findResource(test::parseNameOrDie("@attr/bar"));
+    result = mTable.findResource(test::parseNameOrDie("attr/bar"));
     AAPT_ASSERT_TRUE(result);
 
     AAPT_ASSERT_TRUE(result.value().package->id);
@@ -610,7 +610,7 @@
     ASSERT_TRUE(testParse(input));
 
     Maybe<ResourceTable::SearchResult> result = mTable.findResource(
-            test::parseNameOrDie("@string/bar"));
+            test::parseNameOrDie("string/bar"));
     AAPT_ASSERT_TRUE(result);
     const ResourceEntry* entry = result.value().entry;
     ASSERT_NE(nullptr, entry);
@@ -621,7 +621,7 @@
     std::string input = R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF";
     ASSERT_TRUE(testParse(input));
 
-    BinaryPrimitive* val = test::getValue<BinaryPrimitive>(&mTable, "@integer/foo");
+    BinaryPrimitive* val = test::getValue<BinaryPrimitive>(&mTable, "integer/foo");
     ASSERT_NE(nullptr, val);
 
     EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType);
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index cf6660c..4db40a6 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -30,13 +30,13 @@
     ResourceTable table;
 
     EXPECT_FALSE(table.addResource(
-            test::parseNameOrDie("@android:id/hey,there"),
+            test::parseNameOrDie("android:id/hey,there"),
             ConfigDescription{}, "",
             test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
             test::getDiagnostics()));
 
     EXPECT_FALSE(table.addResource(
-            test::parseNameOrDie("@android:id/hey:there"),
+            test::parseNameOrDie("android:id/hey:there"),
             ConfigDescription{}, "",
             test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
             test::getDiagnostics()));
@@ -46,12 +46,12 @@
     ResourceTable table;
 
     EXPECT_TRUE(table.addResource(
-            test::parseNameOrDie("@android:attr/id"),
+            test::parseNameOrDie("android:attr/id"),
             ConfigDescription{}, "",
             test::ValueBuilder<Id>().setSource("test/path/file.xml", 23u).build(),
             test::getDiagnostics()));
 
-    ASSERT_NE(nullptr, test::getValue<Id>(&table, "@android:attr/id"));
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id"));
 }
 
 TEST(ResourceTableTest, AddMultipleResources) {
@@ -62,35 +62,35 @@
     memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
 
     EXPECT_TRUE(table.addResource(
-            test::parseNameOrDie("@android:attr/layout_width"),
+            test::parseNameOrDie("android:attr/layout_width"),
             config, "",
             test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
             test::getDiagnostics()));
 
     EXPECT_TRUE(table.addResource(
-            test::parseNameOrDie("@android:attr/id"),
+            test::parseNameOrDie("android:attr/id"),
             config, "",
             test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
             test::getDiagnostics()));
 
     EXPECT_TRUE(table.addResource(
-            test::parseNameOrDie("@android:string/ok"),
+            test::parseNameOrDie("android:string/ok"),
             config, "",
             test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
             test::getDiagnostics()));
 
     EXPECT_TRUE(table.addResource(
-            test::parseNameOrDie("@android:string/ok"),
+            test::parseNameOrDie("android:string/ok"),
             languageConfig, "",
             test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
                     .setSource("test/path/file.xml", 20u)
                     .build(),
             test::getDiagnostics()));
 
-    ASSERT_NE(nullptr, test::getValue<Id>(&table, "@android:attr/layout_width"));
-    ASSERT_NE(nullptr, test::getValue<Id>(&table, "@android:attr/id"));
-    ASSERT_NE(nullptr, test::getValue<Id>(&table, "@android:string/ok"));
-    ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(&table, "@android:string/ok",
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/layout_width"));
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id"));
+    ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:string/ok"));
+    ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(&table, "android:string/ok",
                                                                 languageConfig));
 }
 
@@ -98,22 +98,22 @@
     ResourceTable table;
 
     ASSERT_TRUE(table.addResource(
-            test::parseNameOrDie("@android:attr/foo"),
+            test::parseNameOrDie("android:attr/foo"),
             ConfigDescription{}, "",
             util::make_unique<Attribute>(true),
             test::getDiagnostics()));
 
-    Attribute* attr = test::getValue<Attribute>(&table, "@android:attr/foo");
+    Attribute* attr = test::getValue<Attribute>(&table, "android:attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_TRUE(attr->isWeak());
 
     ASSERT_TRUE(table.addResource(
-            test::parseNameOrDie("@android:attr/foo"),
+            test::parseNameOrDie("android:attr/foo"),
             ConfigDescription{}, "",
             util::make_unique<Attribute>(false),
             test::getDiagnostics()));
 
-    attr = test::getValue<Attribute>(&table, "@android:attr/foo");
+    attr = test::getValue<Attribute>(&table, "android:attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_FALSE(attr->isWeak());
 }
@@ -121,24 +121,24 @@
 TEST(ResourceTableTest, ProductVaryingValues) {
     ResourceTable table;
 
-    EXPECT_TRUE(table.addResource(test::parseNameOrDie("@android:string/foo"),
+    EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"),
                                   test::parseConfigOrDie("land"), "tablet",
                                   util::make_unique<Id>(),
                                   test::getDiagnostics()));
-    EXPECT_TRUE(table.addResource(test::parseNameOrDie("@android:string/foo"),
+    EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"),
                                   test::parseConfigOrDie("land"), "phone",
                                   util::make_unique<Id>(),
                                   test::getDiagnostics()));
 
-    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "@android:string/foo",
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/foo",
                                                              test::parseConfigOrDie("land"),
                                                              "tablet"));
-    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "@android:string/foo",
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/foo",
                                                              test::parseConfigOrDie("land"),
                                                              "phone"));
 
     Maybe<ResourceTable::SearchResult> sr = table.findResource(
-            test::parseNameOrDie("@android:string/foo"));
+            test::parseNameOrDie("android:string/foo"));
     AAPT_ASSERT_TRUE(sr);
     std::vector<ResourceConfigValue*> values = sr.value().entry->findAllValues(
             test::parseConfigOrDie("land"));
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 31d6435a6..7dc88ded 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -436,6 +436,22 @@
     return false;
 }
 
+Maybe<ResourceId> tryParseResourceId(const StringPiece& str) {
+    StringPiece trimmedStr(util::trimWhitespace(str));
+
+    std::u16string str16 = util::utf8ToUtf16(trimmedStr);
+    android::Res_value value;
+    if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) {
+        if (value.dataType == android::Res_value::TYPE_INT_HEX) {
+            ResourceId id(value.data);
+            if (id.isValid()) {
+                return id;
+            }
+        }
+    }
+    return {};
+}
+
 Maybe<int> tryParseSdkVersion(const StringPiece& str) {
     StringPiece trimmedStr(util::trimWhitespace(str));
 
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 871ed7c..31b8e89 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -85,6 +85,11 @@
 bool tryParseBool(const StringPiece& str, bool* outValue);
 
 /**
+ * Returns an ID if it the string represented a valid ID.
+ */
+Maybe<ResourceId> tryParseResourceId(const StringPiece& str);
+
+/**
  * Parses an SDK version, which can be an integer, or a letter from A-Z.
  */
 Maybe<int> tryParseSdkVersion(const StringPiece& str);
diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp
index 11eab33..54e9fcd 100644
--- a/tools/aapt2/ValueVisitor_test.cpp
+++ b/tools/aapt2/ValueVisitor_test.cpp
@@ -59,8 +59,8 @@
 
 TEST(ValueVisitorTest, VisitsReferencesInStyle) {
     std::unique_ptr<Style> style = test::StyleBuilder()
-            .setParent("@android:style/foo")
-            .addItem("@android:attr/one", test::buildReference("@android:id/foo"))
+            .setParent("android:style/foo")
+            .addItem("android:attr/one", test::buildReference("android:id/foo"))
             .build();
 
     StyleVisitor visitor;
@@ -73,11 +73,11 @@
 }
 
 TEST(ValueVisitorTest, ValueCast) {
-    std::unique_ptr<Reference> ref = test::buildReference("@android:color/white");
+    std::unique_ptr<Reference> ref = test::buildReference("android:color/white");
     EXPECT_NE(valueCast<Reference>(ref.get()), nullptr);
 
     std::unique_ptr<Style> style = test::StyleBuilder()
-            .addItem("@android:attr/foo", test::buildReference("@android:color/black"))
+            .addItem("android:attr/foo", test::buildReference("android:color/black"))
             .build();
     EXPECT_NE(valueCast<Style>(style.get()), nullptr);
     EXPECT_EQ(valueCast<Reference>(style.get()), nullptr);
diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp
index 341c9b3..501ae9d 100644
--- a/tools/aapt2/compile/IdAssigner.cpp
+++ b/tools/aapt2/compile/IdAssigner.cpp
@@ -19,87 +19,180 @@
 #include "process/IResourceTableConsumer.h"
 #include "util/Util.h"
 
-#include <bitset>
 #include <cassert>
-#include <set>
+#include <map>
 
 namespace aapt {
 
+/**
+ * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and ResourceEntry,
+ * as long as there is no existing ID or the ID is the same.
+ */
+static bool assignId(IDiagnostics* diag, const ResourceId id, const ResourceName& name,
+                     ResourceTablePackage* pkg, ResourceTableType* type, ResourceEntry* entry) {
+    if (pkg->id.value() == id.packageId()) {
+        if (!type->id || type->id.value() == id.typeId()) {
+            type->id = id.typeId();
+
+            if (!entry->id || entry->id.value() == id.entryId()) {
+                entry->id = id.entryId();
+                return true;
+            }
+        }
+    }
+
+    const ResourceId existingId(pkg->id.value(),
+                                type->id ? type->id.value() : 0,
+                                entry->id ? entry->id.value() : 0);
+    diag->error(DiagMessage() << "can't assign ID " << id
+                << " to resource " << name
+                << " with conflicting ID " << existingId);
+    return false;
+}
+
 bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) {
-    std::bitset<256> usedTypeIds;
-    std::set<uint16_t> usedEntryIds;
+    std::map<ResourceId, ResourceName> assignedIds;
 
     for (auto& package : table->packages) {
         assert(package->id && "packages must have manually assigned IDs");
 
-        usedTypeIds.reset();
-
-        // Type ID 0 is invalid, reserve it.
-        usedTypeIds.set(0);
-
-        // Collect used type IDs.
         for (auto& type : package->types) {
-            if (type->id) {
-                usedEntryIds.clear();
+            for (auto& entry : type->entries) {
+                const ResourceName name(package->name, type->type, entry->name);
 
-                if (usedTypeIds[type->id.value()]) {
-                    // This ID is already taken!
-                    context->getDiagnostics()->error(DiagMessage()
-                                                     << "type '" << type->type << "' in "
-                                                     << "package '" << package->name << "' has "
-                                                     << "duplicate ID "
-                                                     << std::hex << (int) type->id.value()
-                                                     << std::dec);
-                    return false;
+                if (mAssignedIdMap) {
+                    // Assign the pre-assigned stable ID meant for this resource.
+                    const auto iter = mAssignedIdMap->find(name);
+                    if (iter != mAssignedIdMap->end()) {
+                        const ResourceId assignedId = iter->second;
+                        const bool result = assignId(context->getDiagnostics(), assignedId, name,
+                                                     package.get(), type.get(), entry.get());
+                        if (!result) {
+                            return false;
+                        }
+                    }
                 }
 
-                // Mark the type ID as taken.
-                usedTypeIds.set(type->id.value());
-            }
-
-            // Collect used entry IDs.
-            for (auto& entry : type->entries) {
-                if (entry->id) {
-                    // Mark entry ID as taken.
-                    if (!usedEntryIds.insert(entry->id.value()).second) {
-                        // This ID existed before!
-                        ResourceNameRef nameRef(package->name, type->type, entry->name);
-                        context->getDiagnostics()->error(DiagMessage()
-                                                         << "resource '" << nameRef << "' "
-                                                         << "has duplicate entry ID "
-                                                         << std::hex << (int) entry->id.value()
-                                                         << std::dec);
+                if (package->id && type->id && entry->id) {
+                    // If the ID is set for this resource, then reserve it.
+                    ResourceId resourceId(package->id.value(), type->id.value(), entry->id.value());
+                    auto result = assignedIds.insert({ resourceId, name });
+                    const ResourceName& existingName = result.first->second;
+                    if (!result.second) {
+                        context->getDiagnostics()->error(DiagMessage() << "resource " << name
+                                                         << " has same ID "
+                                                         << resourceId
+                                                         << " as " << existingName);
                         return false;
                     }
                 }
             }
+        }
+    }
 
-            // Assign unused entry IDs.
-            const auto endUsedEntryIter = usedEntryIds.end();
-            auto nextUsedEntryIter = usedEntryIds.begin();
-            uint16_t nextId = 0;
-            for (auto& entry : type->entries) {
-                if (!entry->id) {
-                    // Assign the next available entryID.
-                    while (nextUsedEntryIter != endUsedEntryIter &&
-                            nextId == *nextUsedEntryIter) {
-                        nextId++;
-                        ++nextUsedEntryIter;
-                    }
-                    entry->id = nextId++;
-                }
+    if (mAssignedIdMap) {
+        // Reserve all the IDs mentioned in the stable ID map. That way we won't assign
+        // IDs that were listed in the map if they don't exist in the table.
+        for (const auto& stableIdEntry : *mAssignedIdMap) {
+            const ResourceName& preAssignedName = stableIdEntry.first;
+            const ResourceId& preAssignedId = stableIdEntry.second;
+            auto result = assignedIds.insert({ preAssignedId, preAssignedName });
+            const ResourceName& existingName = result.first->second;
+            if (!result.second && existingName != preAssignedName) {
+                context->getDiagnostics()->error(DiagMessage() << "stable ID " << preAssignedId
+                                                 << " for resource " << preAssignedName
+                                                 << " is already taken by resource "
+                                                 << existingName);
+                return false;
             }
         }
+    }
 
-        // Assign unused type IDs.
-        size_t nextTypeId = 0;
+    // Assign any resources without IDs the next available ID. Gaps will be filled if possible,
+    // unless those IDs have been reserved.
+
+    const auto assignedIdsIterEnd = assignedIds.end();
+    for (auto& package : table->packages) {
+        assert(package->id && "packages must have manually assigned IDs");
+
+        // Build a half filled ResourceId object, which will be used to find the closest matching
+        // reserved ID in the assignedId map. From that point the next available type ID can be
+        // found.
+        ResourceId resourceId(package->id.value(), 0, 0);
+        uint8_t nextExpectedTypeId = 1;
+
+        // Find the closest matching ResourceId that is <= the one with only the package set.
+        auto nextTypeIter = assignedIds.lower_bound(resourceId);
         for (auto& type : package->types) {
             if (!type->id) {
-                while (nextTypeId < usedTypeIds.size() && usedTypeIds[nextTypeId]) {
-                    nextTypeId++;
+                // We need to assign a type ID. Iterate over the reserved IDs until we find
+                // some type ID that is a distance of 2 greater than the last one we've seen.
+                // That means there is an available type ID between these reserved IDs.
+                while (nextTypeIter != assignedIdsIterEnd) {
+                    if (nextTypeIter->first.packageId() != package->id.value()) {
+                        break;
+                    }
+
+                    const uint8_t typeId = nextTypeIter->first.typeId();
+                    if (typeId > nextExpectedTypeId) {
+                        // There is a gap in the type IDs, so use the missing one.
+                        type->id = nextExpectedTypeId++;
+                        break;
+                    }
+
+                    // Set our expectation to be the next type ID after the reserved one we
+                    // just saw.
+                    nextExpectedTypeId = typeId + 1;
+
+                    // Move to the next reserved ID.
+                    ++nextTypeIter;
                 }
-                type->id = static_cast<uint8_t>(nextTypeId);
-                nextTypeId++;
+
+                if (!type->id) {
+                    // We must have hit the end of the reserved IDs and not found a gap.
+                    // That means the next ID is available.
+                    type->id = nextExpectedTypeId++;
+                }
+            }
+
+            resourceId = ResourceId(package->id.value(), type->id.value(), 0);
+            uint16_t nextExpectedEntryId = 0;
+
+            // Find the closest matching ResourceId that is <= the one with only the package
+            // and type set.
+            auto nextEntryIter = assignedIds.lower_bound(resourceId);
+            for (auto& entry : type->entries) {
+                if (!entry->id) {
+                    // We need to assign an entry ID. Iterate over the reserved IDs until we find
+                    // some entry ID that is a distance of 2 greater than the last one we've seen.
+                    // That means there is an available entry ID between these reserved IDs.
+                    while (nextEntryIter != assignedIdsIterEnd) {
+                        if (nextEntryIter->first.packageId() != package->id.value() ||
+                                nextEntryIter->first.typeId() != type->id.value()) {
+                            break;
+                        }
+
+                        const uint16_t entryId = nextEntryIter->first.entryId();
+                        if (entryId > nextExpectedEntryId) {
+                            // There is a gap in the entry IDs, so use the missing one.
+                            entry->id = nextExpectedEntryId++;
+                            break;
+                        }
+
+                        // Set our expectation to be the next type ID after the reserved one we
+                        // just saw.
+                        nextExpectedEntryId = entryId + 1;
+
+                        // Move to the next reserved entry ID.
+                        ++nextEntryIter;
+                    }
+
+                    if (!entry->id) {
+                        // We must have hit the end of the reserved IDs and not found a gap.
+                        // That means the next ID is available.
+                        entry->id = nextExpectedEntryId++;
+                    }
+                }
             }
         }
     }
diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h
index 514df3a..06cd5e3 100644
--- a/tools/aapt2/compile/IdAssigner.h
+++ b/tools/aapt2/compile/IdAssigner.h
@@ -17,16 +17,29 @@
 #ifndef AAPT_COMPILE_IDASSIGNER_H
 #define AAPT_COMPILE_IDASSIGNER_H
 
+#include "Resource.h"
 #include "process/IResourceTableConsumer.h"
 
+#include <android-base/macros.h>
+#include <unordered_map>
+
 namespace aapt {
 
 /**
  * Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps
  * in between fixed ID assignments.
  */
-struct IdAssigner : public IResourceTableConsumer {
+class IdAssigner : public IResourceTableConsumer {
+public:
+    IdAssigner() = default;
+    explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map) :
+            mAssignedIdMap(map) {
+    }
+
     bool consume(IAaptContext* context, ResourceTable* table) override;
+
+private:
+    const std::unordered_map<ResourceName, ResourceId>* mAssignedIdMap = nullptr;
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp
index 802e99a..d21fcba 100644
--- a/tools/aapt2/compile/IdAssigner_test.cpp
+++ b/tools/aapt2/compile/IdAssigner_test.cpp
@@ -15,11 +15,7 @@
  */
 
 #include "compile/IdAssigner.h"
-
-#include "test/Context.h"
-#include "test/Builders.h"
-
-#include <gtest/gtest.h>
+#include "test/Test.h"
 
 namespace aapt {
 
@@ -27,9 +23,9 @@
 
 TEST(IdAssignerTest, AssignIds) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
-            .addSimple("@android:attr/foo")
-            .addSimple("@android:attr/bar")
-            .addSimple("@android:id/foo")
+            .addSimple("android:attr/foo")
+            .addSimple("android:attr/bar")
+            .addSimple("android:id/foo")
             .setPackageId("android", 0x01)
             .build();
 
@@ -42,10 +38,15 @@
 
 TEST(IdAssignerTest, AssignIdsWithReservedIds) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
-            .addSimple("@android:attr/foo", ResourceId(0x01040006))
-            .addSimple("@android:attr/bar")
-            .addSimple("@android:id/foo")
-            .addSimple("@app:id/biz")
+            .addSimple("android:id/foo", ResourceId(0x01010000))
+            .addSimple("android:dimen/two")
+            .addSimple("android:integer/three")
+            .addSimple("android:string/five")
+            .addSimple("android:attr/fun", ResourceId(0x01040000))
+            .addSimple("android:attr/foo", ResourceId(0x01040006))
+            .addSimple("android:attr/bar")
+            .addSimple("android:attr/baz")
+            .addSimple("app:id/biz")
             .setPackageId("android", 0x01)
             .setPackageId("app", 0x7f)
             .build();
@@ -55,12 +56,40 @@
 
     ASSERT_TRUE(assigner.consume(context.get(), table.get()));
     ASSERT_TRUE(verifyIds(table.get()));
+
+    Maybe<ResourceTable::SearchResult> maybeResult;
+
+    // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX.
+
+    maybeResult = table->findResource(test::parseNameOrDie("android:dimen/two"));
+    AAPT_ASSERT_TRUE(maybeResult);
+    EXPECT_EQ(make_value<uint8_t>(2), maybeResult.value().type->id);
+
+    maybeResult = table->findResource(test::parseNameOrDie("android:integer/three"));
+    AAPT_ASSERT_TRUE(maybeResult);
+    EXPECT_EQ(make_value<uint8_t>(3), maybeResult.value().type->id);
+
+    // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX IDs.
+
+    maybeResult = table->findResource(test::parseNameOrDie("android:string/five"));
+    AAPT_ASSERT_TRUE(maybeResult);
+    EXPECT_EQ(make_value<uint8_t>(5), maybeResult.value().type->id);
+
+    // Expect to fill in the gaps between 0x01040000 and 0x01040006.
+
+    maybeResult = table->findResource(test::parseNameOrDie("android:attr/bar"));
+    AAPT_ASSERT_TRUE(maybeResult);
+    EXPECT_EQ(make_value<uint16_t>(1), maybeResult.value().entry->id);
+
+    maybeResult = table->findResource(test::parseNameOrDie("android:attr/baz"));
+    AAPT_ASSERT_TRUE(maybeResult);
+    EXPECT_EQ(make_value<uint16_t>(2), maybeResult.value().entry->id);
 }
 
 TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
-            .addSimple("@android:attr/foo", ResourceId(0x01040006))
-            .addSimple("@android:attr/bar", ResourceId(0x01040006))
+            .addSimple("android:attr/foo", ResourceId(0x01040006))
+            .addSimple("android:attr/bar", ResourceId(0x01040006))
             .setPackageId("android", 0x01)
             .setPackageId("app", 0x7f)
             .build();
@@ -71,6 +100,29 @@
     ASSERT_FALSE(assigner.consume(context.get(), table.get()));
 }
 
+TEST(IdAssignerTest, AssignIdsWithIdMap) {
+    std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+            .addSimple("android:attr/foo")
+            .addSimple("android:attr/bar")
+            .setPackageId("android", 0x01)
+            .build();
+
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+    std::unordered_map<ResourceName, ResourceId> idMap = {
+            { test::parseNameOrDie("android:attr/foo"), ResourceId(0x01010002) } };
+    IdAssigner assigner(&idMap);
+    ASSERT_TRUE(assigner.consume(context.get(), table.get()));
+    ASSERT_TRUE(verifyIds(table.get()));
+    Maybe<ResourceTable::SearchResult> result = table->findResource(
+            test::parseNameOrDie("android:attr/foo"));
+    AAPT_ASSERT_TRUE(result);
+
+    const ResourceTable::SearchResult& searchResult = result.value();
+    EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.package->id);
+    EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.type->id);
+    EXPECT_EQ(make_value<uint16_t>(0x0002), searchResult.entry->id);
+}
+
 ::testing::AssertionResult verifyIds(ResourceTable* table) {
     std::set<uint8_t> packageIds;
     for (auto& package : table->packages) {
diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
index 1816abc..1f62f90 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp
@@ -71,15 +71,16 @@
 
 TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
-            .addString("@android:string/one", "one")
-            .addString("@android:string/two", ResourceId{}, test::parseConfigOrDie("en"), "two")
-            .addString("@android:string/three", "three")
-            .addString("@android:string/three", ResourceId{}, test::parseConfigOrDie("en-rXA"),
+            .addString("android:string/one", "one")
+            .addString("android:string/two", ResourceId{}, test::parseConfigOrDie("en"), "two")
+            .addString("android:string/three", "three")
+            .addString("android:string/three", ResourceId{}, test::parseConfigOrDie("en-rXA"),
                        "three")
-            .addString("@android:string/four", "four")
+            .addString("android:string/four", "four")
             .build();
 
-    String* val = test::getValue<String>(table.get(), "@android:string/four");
+    String* val = test::getValue<String>(table.get(), "android:string/four");
+    ASSERT_NE(nullptr, val);
     val->setTranslateable(false);
 
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
@@ -87,31 +88,31 @@
     ASSERT_TRUE(generator.consume(context.get(), table.get()));
 
     // Normal pseudolocalization should take place.
-    ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "@android:string/one",
+    ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/one",
                                                        test::parseConfigOrDie("en-rXA")));
-    ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "@android:string/one",
+    ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/one",
                                                        test::parseConfigOrDie("ar-rXB")));
 
     // No default config for android:string/two, so no pseudlocales should exist.
-    ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "@android:string/two",
+    ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/two",
                                                        test::parseConfigOrDie("en-rXA")));
-    ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "@android:string/two",
+    ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/two",
                                                        test::parseConfigOrDie("ar-rXB")));
 
 
     // Check that we didn't override manual pseudolocalization.
-    val = test::getValueForConfig<String>(table.get(), "@android:string/three",
+    val = test::getValueForConfig<String>(table.get(), "android:string/three",
                                           test::parseConfigOrDie("en-rXA"));
     ASSERT_NE(nullptr, val);
     EXPECT_EQ(std::string("three"), *val->value);
 
-    ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "@android:string/three",
+    ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/three",
                                                        test::parseConfigOrDie("ar-rXB")));
 
     // Check that four's translateable marker was honored.
-    ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "@android:string/four",
+    ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/four",
                                                        test::parseConfigOrDie("en-rXA")));
-    ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "@android:string/four",
+    ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/four",
                                                        test::parseConfigOrDie("ar-rXB")));
 
 }
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
index ea1ced3..2c9eab8 100644
--- a/tools/aapt2/compile/XmlIdCollector_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -38,13 +38,13 @@
     ASSERT_TRUE(collector.consume(context.get(), doc.get()));
 
     EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
-                             SourcedResourceName{ test::parseNameOrDie("@id/foo"), 3u }));
+                             SourcedResourceName{ test::parseNameOrDie("id/foo"), 3u }));
 
     EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
-                             SourcedResourceName{ test::parseNameOrDie("@id/bar"), 3u }));
+                             SourcedResourceName{ test::parseNameOrDie("id/bar"), 3u }));
 
     EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(),
-                             SourcedResourceName{ test::parseNameOrDie("@id/car"), 6u }));
+                             SourcedResourceName{ test::parseNameOrDie("id/car"), 6u }));
 }
 
 TEST(XmlIdCollectorTest, DontCollectNonIds) {
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index e720e7e..0b92ba5 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -132,65 +132,65 @@
 TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("com.app.test", 0x7f)
-            .addSimple("@com.app.test:id/one", ResourceId(0x7f020000))
-            .addSimple("@com.app.test:id/two", ResourceId(0x7f020001))
-            .addValue("@com.app.test:id/three", ResourceId(0x7f020002),
-                      test::buildReference("@com.app.test:id/one", ResourceId(0x7f020000)))
-            .addValue("@com.app.test:integer/one", ResourceId(0x7f030000),
+            .addSimple("com.app.test:id/one", ResourceId(0x7f020000))
+            .addSimple("com.app.test:id/two", ResourceId(0x7f020001))
+            .addValue("com.app.test:id/three", ResourceId(0x7f020002),
+                      test::buildReference("com.app.test:id/one", ResourceId(0x7f020000)))
+            .addValue("com.app.test:integer/one", ResourceId(0x7f030000),
                       util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u))
-            .addValue("@com.app.test:integer/one", test::parseConfigOrDie("v1"),
+            .addValue("com.app.test:integer/one", test::parseConfigOrDie("v1"),
                       ResourceId(0x7f030000),
                       util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u))
-            .addString("@com.app.test:string/test", ResourceId(0x7f040000), "foo")
-            .addString("@com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
+            .addString("com.app.test:string/test", ResourceId(0x7f040000), "foo")
+            .addString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml")
             .build();
 
     ResTable resTable;
     ASSERT_TRUE(flatten(table.get(), &resTable));
 
-    EXPECT_TRUE(exists(&resTable, "@com.app.test:id/one", ResourceId(0x7f020000), {},
+    EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020000), {},
                        Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
 
-    EXPECT_TRUE(exists(&resTable, "@com.app.test:id/two", ResourceId(0x7f020001), {},
+    EXPECT_TRUE(exists(&resTable, "com.app.test:id/two", ResourceId(0x7f020001), {},
                        Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
 
-    EXPECT_TRUE(exists(&resTable, "@com.app.test:id/three", ResourceId(0x7f020002), {},
+    EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020002), {},
                        Res_value::TYPE_REFERENCE, 0x7f020000u, 0u));
 
-    EXPECT_TRUE(exists(&resTable, "@com.app.test:integer/one", ResourceId(0x7f030000),
+    EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one", ResourceId(0x7f030000),
                        {}, Res_value::TYPE_INT_DEC, 1u,
                        ResTable_config::CONFIG_VERSION));
 
-    EXPECT_TRUE(exists(&resTable, "@com.app.test:integer/one", ResourceId(0x7f030000),
+    EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one", ResourceId(0x7f030000),
                        test::parseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u,
                        ResTable_config::CONFIG_VERSION));
 
     std::u16string fooStr = u"foo";
     ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(), fooStr.size());
     ASSERT_GE(idx, 0);
-    EXPECT_TRUE(exists(&resTable, "@com.app.test:string/test", ResourceId(0x7f040000),
+    EXPECT_TRUE(exists(&resTable, "com.app.test:string/test", ResourceId(0x7f040000),
                        {}, Res_value::TYPE_STRING, (uint32_t) idx, 0u));
 
     std::u16string barPath = u"res/layout/bar.xml";
     idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(), barPath.size());
     ASSERT_GE(idx, 0);
-    EXPECT_TRUE(exists(&resTable, "@com.app.test:layout/bar", ResourceId(0x7f050000), {},
+    EXPECT_TRUE(exists(&resTable, "com.app.test:layout/bar", ResourceId(0x7f050000), {},
                        Res_value::TYPE_STRING, (uint32_t) idx, 0u));
 }
 
 TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("com.app.test", 0x7f)
-            .addSimple("@com.app.test:id/one", ResourceId(0x7f020001))
-            .addSimple("@com.app.test:id/three", ResourceId(0x7f020003))
+            .addSimple("com.app.test:id/one", ResourceId(0x7f020001))
+            .addSimple("com.app.test:id/three", ResourceId(0x7f020003))
             .build();
 
     ResTable resTable;
     ASSERT_TRUE(flatten(table.get(), &resTable));
 
-    EXPECT_TRUE(exists(&resTable, "@com.app.test:id/one", ResourceId(0x7f020001), {},
+    EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020001), {},
                        Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
-    EXPECT_TRUE(exists(&resTable, "@com.app.test:id/three", ResourceId(0x7f020003), {},
+    EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020003), {},
                        Res_value::TYPE_INT_BOOLEAN, 0u, 0u));
 }
 
@@ -201,14 +201,14 @@
     attr.maxInt = 23;
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("android", 0x01)
-            .addValue("@android:attr/foo", ResourceId(0x01010000),
+            .addValue("android:attr/foo", ResourceId(0x01010000),
                       util::make_unique<Attribute>(attr))
             .build();
 
     ResourceTable result;
     ASSERT_TRUE(flatten(table.get(), &result));
 
-    Attribute* actualAttr = test::getValue<Attribute>(&result, "@android:attr/foo");
+    Attribute* actualAttr = test::getValue<Attribute>(&result, "android:attr/foo");
     ASSERT_NE(nullptr, actualAttr);
     EXPECT_EQ(attr.isWeak(), actualAttr->isWeak());
     EXPECT_EQ(attr.typeMask, actualAttr->typeMask);
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index 9efee1e..8b3378f 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -31,12 +31,12 @@
                 .setCompilationPackage("com.app.test")
                 .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" })
                 .addSymbolSource(test::StaticSymbolSourceBuilder()
-                        .addSymbol("@android:attr/id", ResourceId(0x010100d0),
+                        .addSymbol("android:attr/id", ResourceId(0x010100d0),
                                    test::AttributeBuilder().build())
-                        .addSymbol("@com.app.test:id/id", ResourceId(0x7f020000))
-                        .addSymbol("@android:attr/paddingStart", ResourceId(0x010103b3),
+                        .addSymbol("com.app.test:id/id", ResourceId(0x7f020000))
+                        .addSymbol("android:attr/paddingStart", ResourceId(0x010103b3),
                                    test::AttributeBuilder().build())
-                        .addSymbol("@android:attr/colorAccent", ResourceId(0x01010435),
+                        .addSymbol("android:attr/colorAccent", ResourceId(0x01010435),
                                    test::AttributeBuilder().build())
                         .build())
                 .build();
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 57a8047..ed7c6bd 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -26,7 +26,7 @@
 TEST(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("android", 0x01)
-            .addSimple("@android:id/class", ResourceId(0x01020000))
+            .addSimple("android:id/class", ResourceId(0x01020000))
             .build();
 
     std::unique_ptr<IAaptContext> context = test::ContextBuilder()
@@ -42,12 +42,12 @@
 TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("android", 0x01)
-            .addSimple("@android:id/hey-man", ResourceId(0x01020000))
-            .addValue("@android:attr/cool.attr", ResourceId(0x01010000),
+            .addSimple("android:id/hey-man", ResourceId(0x01020000))
+            .addValue("android:attr/cool.attr", ResourceId(0x01010000),
                       test::AttributeBuilder(false).build())
-            .addValue("@android:styleable/hey.dude", ResourceId(0x01030000),
+            .addValue("android:styleable/hey.dude", ResourceId(0x01030000),
                       test::StyleableBuilder()
-                              .addItem("@android:attr/cool.attr", ResourceId(0x01010000))
+                              .addItem("android:attr/cool.attr", ResourceId(0x01010000))
                               .build())
             .build();
 
@@ -75,8 +75,8 @@
 TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("android", 0x01)
-            .addSimple("@android:id/one", ResourceId(0x01020000))
-            .addSimple("@android:id/com.foo$two", ResourceId(0x01020001))
+            .addSimple("android:id/one", ResourceId(0x01020000))
+            .addSimple("android:id/com.foo$two", ResourceId(0x01020001))
             .build();
 
     std::unique_ptr<IAaptContext> context = test::ContextBuilder()
@@ -97,8 +97,8 @@
 TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("android", 0x01)
-            .addSimple("@android:attr/two", ResourceId(0x01010001))
-            .addSimple("@android:^attr-private/one", ResourceId(0x01010000))
+            .addSimple("android:attr/two", ResourceId(0x01010001))
+            .addSimple("android:^attr-private/one", ResourceId(0x01010000))
             .build();
 
     std::unique_ptr<IAaptContext> context = test::ContextBuilder()
@@ -118,11 +118,11 @@
     StdErrDiagnostics diag;
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("android", 0x01)
-            .addSimple("@android:id/one", ResourceId(0x01020000))
-            .addSimple("@android:id/two", ResourceId(0x01020001))
-            .addSimple("@android:id/three", ResourceId(0x01020002))
-            .setSymbolState("@android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
-            .setSymbolState("@android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
+            .addSimple("android:id/one", ResourceId(0x01020000))
+            .addSimple("android:id/two", ResourceId(0x01020001))
+            .addSimple("android:id/three", ResourceId(0x01020002))
+            .setSymbolState("android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
+            .setSymbolState("android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
             .build();
 
     std::unique_ptr<IAaptContext> context = test::ContextBuilder()
@@ -200,14 +200,14 @@
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
                 .setPackageId("android", 0x01)
                 .setPackageId("com.lib", 0x02)
-                .addValue("@android:attr/bar", ResourceId(0x01010000),
+                .addValue("android:attr/bar", ResourceId(0x01010000),
                           test::AttributeBuilder(false).build())
-                .addValue("@com.lib:attr/bar", ResourceId(0x02010000),
+                .addValue("com.lib:attr/bar", ResourceId(0x02010000),
                            test::AttributeBuilder(false).build())
-                .addValue("@android:styleable/foo", ResourceId(0x01030000),
+                .addValue("android:styleable/foo", ResourceId(0x01030000),
                           test::StyleableBuilder()
-                                  .addItem("@android:attr/bar", ResourceId(0x01010000))
-                                  .addItem("@com.lib:attr/bar", ResourceId(0x02010000))
+                                  .addItem("android:attr/bar", ResourceId(0x01010000))
+                                  .addItem("com.lib:attr/bar", ResourceId(0x02010000))
                                   .build())
                 .build();
 
@@ -228,9 +228,9 @@
 TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("android", 0x01)
-            .addSimple("@android:id/foo", ResourceId(0x01010000))
+            .addSimple("android:id/foo", ResourceId(0x01010000))
             .build();
-    test::getValue<Id>(table.get(), "@android:id/foo")
+    test::getValue<Id>(table.get(), "android:id/foo")
             ->setComment(std::string("This is a comment\n@deprecated"));
 
     std::unique_ptr<IAaptContext> context = test::ContextBuilder()
@@ -262,13 +262,13 @@
     attr.setComment(StringPiece("This is an attribute"));
 
     Styleable styleable;
-    styleable.entries.push_back(Reference(test::parseNameOrDie("@android:attr/one")));
+    styleable.entries.push_back(Reference(test::parseNameOrDie("android:attr/one")));
     styleable.setComment(StringPiece("This is a styleable"));
 
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("android", 0x01)
-            .addValue("@android:attr/one", util::make_unique<Attribute>(attr))
-            .addValue("@android:styleable/Container",
+            .addValue("android:attr/one", util::make_unique<Attribute>(attr))
+            .addValue("android:styleable/Container",
                       std::unique_ptr<Styleable>(styleable.clone(nullptr)))
             .build();
 
@@ -283,19 +283,19 @@
     ASSERT_TRUE(generator.generate("android", &out));
     std::string actual = out.str();
 
-    EXPECT_NE(std::string::npos, actual.find("@attr name android:one"));
-    EXPECT_NE(std::string::npos, actual.find("@attr description"));
+    EXPECT_NE(std::string::npos, actual.find("attr name android:one"));
+    EXPECT_NE(std::string::npos, actual.find("attr description"));
     EXPECT_NE(std::string::npos, actual.find(attr.getComment().data()));
     EXPECT_NE(std::string::npos, actual.find(styleable.getComment().data()));
 }
 
 TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) {
     Attribute attr(false);
-    attr.setComment(StringPiece("@removed"));
+    attr.setComment(StringPiece("removed"));
 
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("android", 0x01)
-            .addValue("@android:attr/one", util::make_unique<Attribute>(attr))
+            .addValue("android:attr/one", util::make_unique<Attribute>(attr))
             .build();
 
     std::unique_ptr<IAaptContext> context = test::ContextBuilder()
@@ -314,9 +314,9 @@
 
     // We should find @removed only in the attribute javadoc and not anywhere else (i.e. the class
     // javadoc).
-    const size_t pos = actual.find("@removed");
+    const size_t pos = actual.find("removed");
     EXPECT_NE(std::string::npos, pos);
-    EXPECT_EQ(std::string::npos, actual.find("@removed", pos + 1));
+    EXPECT_EQ(std::string::npos, actual.find("removed", pos + 1));
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp
index 7764176..3a61da5 100644
--- a/tools/aapt2/link/AutoVersioner_test.cpp
+++ b/tools/aapt2/link/AutoVersioner_test.cpp
@@ -51,20 +51,20 @@
 TEST(AutoVersionerTest, VersionStylesForTable) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("app", 0x7f)
-            .addValue("@app:style/Foo", test::parseConfigOrDie("v4"), ResourceId(0x7f020000),
+            .addValue("app:style/Foo", test::parseConfigOrDie("v4"), ResourceId(0x7f020000),
                       test::StyleBuilder()
-                            .addItem("@android:attr/onClick", ResourceId(0x0101026f),
+                            .addItem("android:attr/onClick", ResourceId(0x0101026f),
                                      util::make_unique<Id>())
-                            .addItem("@android:attr/paddingStart", ResourceId(0x010103b3),
+                            .addItem("android:attr/paddingStart", ResourceId(0x010103b3),
                                      util::make_unique<Id>())
-                            .addItem("@android:attr/requiresSmallestWidthDp",
+                            .addItem("android:attr/requiresSmallestWidthDp",
                                      ResourceId(0x01010364), util::make_unique<Id>())
-                            .addItem("@android:attr/colorAccent", ResourceId(0x01010435),
+                            .addItem("android:attr/colorAccent", ResourceId(0x01010435),
                                      util::make_unique<Id>())
                             .build())
-            .addValue("@app:style/Foo", test::parseConfigOrDie("v21"), ResourceId(0x7f020000),
+            .addValue("app:style/Foo", test::parseConfigOrDie("v21"), ResourceId(0x7f020000),
                       test::StyleBuilder()
-                            .addItem("@android:attr/paddingEnd", ResourceId(0x010103b4),
+                            .addItem("android:attr/paddingEnd", ResourceId(0x010103b4),
                                      util::make_unique<Id>())
                             .build())
             .build();
@@ -77,46 +77,46 @@
     AutoVersioner versioner;
     ASSERT_TRUE(versioner.consume(context.get(), table.get()));
 
-    Style* style = test::getValueForConfig<Style>(table.get(), "@app:style/Foo",
+    Style* style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
                                                   test::parseConfigOrDie("v4"));
     ASSERT_NE(style, nullptr);
     ASSERT_EQ(style->entries.size(), 1u);
     AAPT_ASSERT_TRUE(style->entries.front().key.name);
     EXPECT_EQ(style->entries.front().key.name.value(),
-              test::parseNameOrDie("@android:attr/onClick"));
+              test::parseNameOrDie("android:attr/onClick"));
 
-    style = test::getValueForConfig<Style>(table.get(), "@app:style/Foo",
+    style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
                                            test::parseConfigOrDie("v13"));
     ASSERT_NE(style, nullptr);
     ASSERT_EQ(style->entries.size(), 2u);
     AAPT_ASSERT_TRUE(style->entries[0].key.name);
     EXPECT_EQ(style->entries[0].key.name.value(),
-              test::parseNameOrDie("@android:attr/onClick"));
+              test::parseNameOrDie("android:attr/onClick"));
     AAPT_ASSERT_TRUE(style->entries[1].key.name);
     EXPECT_EQ(style->entries[1].key.name.value(),
-                  test::parseNameOrDie("@android:attr/requiresSmallestWidthDp"));
+                  test::parseNameOrDie("android:attr/requiresSmallestWidthDp"));
 
-    style = test::getValueForConfig<Style>(table.get(), "@app:style/Foo",
+    style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
                                            test::parseConfigOrDie("v17"));
     ASSERT_NE(style, nullptr);
     ASSERT_EQ(style->entries.size(), 3u);
     AAPT_ASSERT_TRUE(style->entries[0].key.name);
     EXPECT_EQ(style->entries[0].key.name.value(),
-                  test::parseNameOrDie("@android:attr/onClick"));
+                  test::parseNameOrDie("android:attr/onClick"));
     AAPT_ASSERT_TRUE(style->entries[1].key.name);
     EXPECT_EQ(style->entries[1].key.name.value(),
-                  test::parseNameOrDie("@android:attr/requiresSmallestWidthDp"));
+                  test::parseNameOrDie("android:attr/requiresSmallestWidthDp"));
     AAPT_ASSERT_TRUE(style->entries[2].key.name);
     EXPECT_EQ(style->entries[2].key.name.value(),
-                  test::parseNameOrDie("@android:attr/paddingStart"));
+                  test::parseNameOrDie("android:attr/paddingStart"));
 
-    style = test::getValueForConfig<Style>(table.get(), "@app:style/Foo",
+    style = test::getValueForConfig<Style>(table.get(), "app:style/Foo",
                                            test::parseConfigOrDie("v21"));
     ASSERT_NE(style, nullptr);
     ASSERT_EQ(style->entries.size(), 1u);
     AAPT_ASSERT_TRUE(style->entries.front().key.name);
     EXPECT_EQ(style->entries.front().key.name.value(),
-              test::parseNameOrDie("@android:attr/paddingEnd"));
+              test::parseNameOrDie("android:attr/paddingEnd"));
 }
 
 } // namespace aapt
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 8093e6a..ded661e 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -44,10 +44,12 @@
 #include "util/StringPiece.h"
 #include "xml/XmlDom.h"
 
+#include <android-base/file.h>
 #include <google/protobuf/io/coded_stream.h>
 
 #include <fstream>
 #include <sys/stat.h>
+#include <unordered_map>
 #include <vector>
 
 namespace aapt {
@@ -76,6 +78,8 @@
     ManifestFixerOptions manifestFixerOptions;
     std::unordered_set<std::string> products;
     TableSplitterOptions tableSplitterOptions;
+    std::unordered_map<ResourceName, ResourceId> stableIdMap;
+    Maybe<std::string> resourceIdMapPath;
 };
 
 class LinkContext : public IAaptContext {
@@ -517,6 +521,77 @@
     return !error;
 }
 
+static bool writeStableIdMapToPath(IDiagnostics* diag,
+                                   const std::unordered_map<ResourceName, ResourceId>& idMap,
+                                   const std::string idMapPath) {
+    std::ofstream fout(idMapPath, std::ofstream::binary);
+    if (!fout) {
+        diag->error(DiagMessage(idMapPath) << strerror(errno));
+        return false;
+    }
+
+    for (const auto& entry : idMap) {
+        const ResourceName& name = entry.first;
+        const ResourceId& id = entry.second;
+        fout << name << " = " << id << "\n";
+    }
+
+    if (!fout) {
+        diag->error(DiagMessage(idMapPath) << "failed writing to file: " << strerror(errno));
+        return false;
+    }
+
+    return true;
+}
+
+static bool loadStableIdMap(IDiagnostics* diag, const std::string& path,
+                            std::unordered_map<ResourceName, ResourceId>* outIdMap) {
+    std::string content;
+    if (!android::base::ReadFileToString(path, &content)) {
+        diag->error(DiagMessage(path) << "failed reading stable ID file");
+        return false;
+    }
+
+    outIdMap->clear();
+    size_t lineNo = 0;
+    for (StringPiece line : util::tokenize(content, '\n')) {
+        lineNo++;
+        line = util::trimWhitespace(line);
+        if (line.empty()) {
+            continue;
+        }
+
+        auto iter = std::find(line.begin(), line.end(), '=');
+        if (iter == line.end()) {
+            diag->error(DiagMessage(Source(path, lineNo)) << "missing '='");
+            return false;
+        }
+
+        ResourceNameRef name;
+        StringPiece resNameStr = util::trimWhitespace(
+                line.substr(0, std::distance(line.begin(), iter)));
+        if (!ResourceUtils::parseResourceName(resNameStr, &name)) {
+            diag->error(DiagMessage(Source(path, lineNo))
+                        << "invalid resource name '" << resNameStr << "'");
+            return false;
+        }
+
+        const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1;
+        const size_t resIdStrLen = line.size() - resIdStartIdx;
+        StringPiece resIdStr = util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen));
+
+        Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(resIdStr);
+        if (!maybeId) {
+            diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '"
+                        << resIdStr << "'");
+            return false;
+        }
+
+        (*outIdMap)[name.toResourceName()] = maybeId.value();
+    }
+    return true;
+}
+
 class LinkCommand {
 public:
     LinkCommand(LinkContext* context, const LinkOptions& options) :
@@ -1176,11 +1251,32 @@
 
         if (!mOptions.staticLib) {
             // Assign IDs if we are building a regular app.
-            IdAssigner idAssigner;
+            IdAssigner idAssigner(&mOptions.stableIdMap);
             if (!idAssigner.consume(mContext, &mFinalTable)) {
                 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
                 return 1;
             }
+
+            // Now grab each ID and emit it as a file.
+            if (mOptions.resourceIdMapPath) {
+                for (auto& package : mFinalTable.packages) {
+                    for (auto& type : package->types) {
+                        for (auto& entry : type->entries) {
+                            ResourceName name(package->name, type->type, entry->name);
+                            // The IDs are guaranteed to exist.
+                            mOptions.stableIdMap[std::move(name)] = ResourceId(package->id.value(),
+                                                                               type->id.value(),
+                                                                               entry->id.value());
+                        }
+                    }
+                }
+
+                if (!writeStableIdMapToPath(mContext->getDiagnostics(),
+                                            mOptions.stableIdMap,
+                                            mOptions.resourceIdMapPath.value())) {
+                    return 1;
+                }
+            }
         } else {
             // Static libs are merged with other apps, and ID collisions are bad, so verify that
             // no IDs have been set.
@@ -1437,6 +1533,7 @@
     bool legacyXFlag = false;
     bool requireLocalization = false;
     bool verbose = false;
+    Maybe<std::string> stableIdFilePath;
     Flags flags = Flags()
             .requiredFlag("-o", "Output path", &options.outputPath)
             .requiredFlag("--manifest", "Path to the Android manifest to build",
@@ -1493,6 +1590,11 @@
             .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
                             "This is implied when --static-lib is specified.",
                             &options.generateNonFinalIds)
+            .optionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
+                          &stableIdFilePath)
+            .optionalFlag("--emit-ids", "Emit a file at the given path with a list of name to ID\n"
+                          "mappings, suitable for use with --stable-ids.",
+                          &options.resourceIdMapPath)
             .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
                           "private symbols.\n"
                           "If not specified, public and private symbols will use the application's "
@@ -1619,6 +1721,13 @@
         options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
     }
 
+    if (!options.staticLib && stableIdFilePath) {
+        if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(),
+                             &options.stableIdMap)) {
+            return 1;
+        }
+    }
+
     // Turn off auto versioning for static-libs.
     if (options.staticLib) {
         options.noAutoVersion = true;
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 6d52c4c..1c69a8c 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -31,21 +31,21 @@
                 .setPackageId(0x01)
                 .setNameManglerPolicy(NameManglerPolicy{ "android" })
                 .addSymbolSource(test::StaticSymbolSourceBuilder()
-                        .addSymbol("@android:attr/package", ResourceId(0x01010000),
+                        .addSymbol("android:attr/package", ResourceId(0x01010000),
                                    test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_STRING)
                                         .build())
-                        .addSymbol("@android:attr/minSdkVersion", ResourceId(0x01010001),
+                        .addSymbol("android:attr/minSdkVersion", ResourceId(0x01010001),
                                    test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_STRING |
                                                      android::ResTable_map::TYPE_INTEGER)
                                         .build())
-                        .addSymbol("@android:attr/targetSdkVersion", ResourceId(0x01010002),
+                        .addSymbol("android:attr/targetSdkVersion", ResourceId(0x01010002),
                                    test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_STRING |
                                                      android::ResTable_map::TYPE_INTEGER)
                                         .build())
-                        .addSymbol("@android:string/str", ResourceId(0x01060000))
+                        .addSymbol("android:string/str", ResourceId(0x01060000))
                         .build())
                 .build();
     }
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index 136e10b..c9d1a08 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -23,12 +23,12 @@
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
 
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
-            .addSimple("@android:attr/publicA")
-            .addSimple("@android:attr/privateA")
-            .addSimple("@android:attr/publicB")
-            .addSimple("@android:attr/privateB")
-            .setSymbolState("@android:attr/publicA", ResourceId(0x01010000), SymbolState::kPublic)
-            .setSymbolState("@android:attr/publicB", ResourceId(0x01010000), SymbolState::kPublic)
+            .addSimple("android:attr/publicA")
+            .addSimple("android:attr/privateA")
+            .addSimple("android:attr/publicB")
+            .addSimple("android:attr/privateB")
+            .setSymbolState("android:attr/publicA", ResourceId(0x01010000), SymbolState::kPublic)
+            .setSymbolState("android:attr/publicB", ResourceId(0x01010000), SymbolState::kPublic)
             .build();
 
     PrivateAttributeMover mover;
@@ -54,8 +54,8 @@
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
 
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
-            .addSimple("@android:attr/privateA")
-            .addSimple("@android:attr/privateB")
+            .addSimple("android:attr/privateA")
+            .addSimple("android:attr/privateB")
             .build();
 
     PrivateAttributeMover mover;
diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp
index 811323b..a3376ac 100644
--- a/tools/aapt2/link/ProductFilter_test.cpp
+++ b/tools/aapt2/link/ProductFilter_test.cpp
@@ -26,23 +26,23 @@
     const ConfigDescription port = test::parseConfigOrDie("port");
 
     ResourceTable table;
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie("@android:string/one"),
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
                                   land, "",
                                   test::ValueBuilder<Id>()
                                           .setSource(Source("land/default.xml")).build(),
                                   context->getDiagnostics()));
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie("@android:string/one"),
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
                                   land, "tablet",
                                   test::ValueBuilder<Id>()
                                           .setSource(Source("land/tablet.xml")).build(),
                                   context->getDiagnostics()));
 
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie("@android:string/one"),
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
                                   port, "",
                                   test::ValueBuilder<Id>()
                                           .setSource(Source("port/default.xml")).build(),
                                   context->getDiagnostics()));
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie("@android:string/one"),
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
                                   port, "tablet",
                                   test::ValueBuilder<Id>()
                                           .setSource(Source("port/tablet.xml")).build(),
@@ -51,13 +51,13 @@
     ProductFilter filter({ "tablet" });
     ASSERT_TRUE(filter.consume(context.get(), &table));
 
-    EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "@android:string/one",
+    EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
                                                              land, ""));
-    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "@android:string/one",
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
                                                              land, "tablet"));
-    EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "@android:string/one",
+    EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
                                                              port, ""));
-    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "@android:string/one",
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
                                                              port, "tablet"));
 }
 
@@ -65,12 +65,12 @@
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
 
     ResourceTable table;
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie("@android:string/one"),
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
                                   ConfigDescription::defaultConfig(), "",
                                   test::ValueBuilder<Id>()
                                           .setSource(Source("default.xml")).build(),
                                   context->getDiagnostics()));
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie("@android:string/one"),
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
                                   ConfigDescription::defaultConfig(), "tablet",
                                   test::ValueBuilder<Id>()
                                           .setSource(Source("tablet.xml")).build(),
@@ -79,10 +79,10 @@
     ProductFilter filter({});
     ASSERT_TRUE(filter.consume(context.get(), &table));
 
-    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "@android:string/one",
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
                                                              ConfigDescription::defaultConfig(),
                                                              ""));
-    EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "@android:string/one",
+    EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one",
                                                              ConfigDescription::defaultConfig(),
                                                              "tablet"));
 }
@@ -91,17 +91,17 @@
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
 
     ResourceTable table;
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie("@android:string/one"),
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
                                   ConfigDescription::defaultConfig(), "",
                                   test::ValueBuilder<Id>()
                                           .setSource(Source("default.xml")).build(),
                                   context->getDiagnostics()));
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie("@android:string/one"),
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
                                   ConfigDescription::defaultConfig(), "tablet",
                                   test::ValueBuilder<Id>()
                                           .setSource(Source("tablet.xml")).build(),
                                   context->getDiagnostics()));
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie("@android:string/one"),
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
                                   ConfigDescription::defaultConfig(), "no-sdcard",
                                   test::ValueBuilder<Id>()
                                           .setSource(Source("no-sdcard.xml")).build(),
@@ -115,12 +115,12 @@
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
 
     ResourceTable table;
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie("@android:string/one"),
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
                                   ConfigDescription::defaultConfig(), "",
                                   test::ValueBuilder<Id>()
                                           .setSource(Source(".xml")).build(),
                                   context->getDiagnostics()));
-    ASSERT_TRUE(table.addResource(test::parseNameOrDie("@android:string/one"),
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"),
                                   ConfigDescription::defaultConfig(), "default",
                                   test::ValueBuilder<Id>()
                                           .setSource(Source("default.xml")).build(),
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
index 17c2636..5c1511f 100644
--- a/tools/aapt2/link/ReferenceLinker_test.cpp
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -24,14 +24,14 @@
 TEST(ReferenceLinkerTest, LinkSimpleReferences) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("com.app.test", 0x7f)
-            .addReference("@com.app.test:string/foo", ResourceId(0x7f020000),
-                          "@com.app.test:string/bar")
+            .addReference("com.app.test:string/foo", ResourceId(0x7f020000),
+                          "com.app.test:string/bar")
 
             // Test use of local reference (w/o package name).
-            .addReference("@com.app.test:string/bar", ResourceId(0x7f020001), "@string/baz")
+            .addReference("com.app.test:string/bar", ResourceId(0x7f020001), "string/baz")
 
-            .addReference("@com.app.test:string/baz", ResourceId(0x7f020002),
-                          "@android:string/ok")
+            .addReference("com.app.test:string/baz", ResourceId(0x7f020002),
+                          "android:string/ok")
             .build();
 
     std::unique_ptr<IAaptContext> context = test::ContextBuilder()
@@ -40,24 +40,24 @@
             .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" })
             .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
             .addSymbolSource(test::StaticSymbolSourceBuilder()
-                                     .addPublicSymbol("@android:string/ok", ResourceId(0x01040034))
+                                     .addPublicSymbol("android:string/ok", ResourceId(0x01040034))
                                      .build())
             .build();
 
     ReferenceLinker linker;
     ASSERT_TRUE(linker.consume(context.get(), table.get()));
 
-    Reference* ref = test::getValue<Reference>(table.get(), "@com.app.test:string/foo");
+    Reference* ref = test::getValue<Reference>(table.get(), "com.app.test:string/foo");
     ASSERT_NE(ref, nullptr);
     AAPT_ASSERT_TRUE(ref->id);
     EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001));
 
-    ref = test::getValue<Reference>(table.get(), "@com.app.test:string/bar");
+    ref = test::getValue<Reference>(table.get(), "com.app.test:string/bar");
     ASSERT_NE(ref, nullptr);
     AAPT_ASSERT_TRUE(ref->id);
     EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002));
 
-    ref = test::getValue<Reference>(table.get(), "@com.app.test:string/baz");
+    ref = test::getValue<Reference>(table.get(), "com.app.test:string/baz");
     ASSERT_NE(ref, nullptr);
     AAPT_ASSERT_TRUE(ref->id);
     EXPECT_EQ(ref->id.value(), ResourceId(0x01040034));
@@ -66,17 +66,17 @@
 TEST(ReferenceLinkerTest, LinkStyleAttributes) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("com.app.test", 0x7f)
-            .addValue("@com.app.test:style/Theme", test::StyleBuilder()
-                    .setParent("@android:style/Theme.Material")
-                    .addItem("@android:attr/foo", ResourceUtils::tryParseColor("#ff00ff"))
-                    .addItem("@android:attr/bar", {} /* placeholder */)
+            .addValue("com.app.test:style/Theme", test::StyleBuilder()
+                    .setParent("android:style/Theme.Material")
+                    .addItem("android:attr/foo", ResourceUtils::tryParseColor("#ff00ff"))
+                    .addItem("android:attr/bar", {} /* placeholder */)
                     .build())
             .build();
 
     {
         // We need to fill in the value for the attribute android:attr/bar after we build the
         // table, because we need access to the string pool.
-        Style* style = test::getValue<Style>(table.get(), "@com.app.test:style/Theme");
+        Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme");
         ASSERT_NE(style, nullptr);
         style->entries.back().value = util::make_unique<RawString>(
                 table->stringPool.makeRef("one|two"));
@@ -87,13 +87,13 @@
             .setPackageId(0x7f)
             .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" })
             .addSymbolSource(test::StaticSymbolSourceBuilder()
-                                     .addPublicSymbol("@android:style/Theme.Material",
+                                     .addPublicSymbol("android:style/Theme.Material",
                                                       ResourceId(0x01060000))
-                                     .addPublicSymbol("@android:attr/foo", ResourceId(0x01010001),
+                                     .addPublicSymbol("android:attr/foo", ResourceId(0x01010001),
                                                       test::AttributeBuilder()
                                                               .setTypeMask(ResTable_map::TYPE_COLOR)
                                                               .build())
-                                     .addPublicSymbol("@android:attr/bar", ResourceId(0x01010002),
+                                     .addPublicSymbol("android:attr/bar", ResourceId(0x01010002),
                                                       test::AttributeBuilder()
                                                               .setTypeMask(ResTable_map::TYPE_FLAGS)
                                                               .addItem("one", 0x01)
@@ -105,7 +105,7 @@
     ReferenceLinker linker;
     ASSERT_TRUE(linker.consume(context.get(), table.get()));
 
-    Style* style = test::getValue<Style>(table.get(), "@com.app.test:style/Theme");
+    Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme");
     ASSERT_NE(style, nullptr);
     AAPT_ASSERT_TRUE(style->parent);
     AAPT_ASSERT_TRUE(style->parent.value().id);
@@ -128,7 +128,7 @@
             .setPackageId(0x7f)
             .setNameManglerPolicy(NameManglerPolicy{ "com.app.test", { "com.android.support" } })
             .addSymbolSource(test::StaticSymbolSourceBuilder()
-                                     .addPublicSymbol("@com.app.test:attr/com.android.support$foo",
+                                     .addPublicSymbol("com.app.test:attr/com.android.support$foo",
                                                       ResourceId(0x7f010000),
                                                       test::AttributeBuilder()
                                                               .setTypeMask(ResTable_map::TYPE_COLOR)
@@ -138,8 +138,8 @@
 
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("com.app.test", 0x7f)
-            .addValue("@com.app.test:style/Theme", ResourceId(0x7f020000),
-                      test::StyleBuilder().addItem("@com.android.support:attr/foo",
+            .addValue("com.app.test:style/Theme", ResourceId(0x7f020000),
+                      test::StyleBuilder().addItem("com.android.support:attr/foo",
                                                    ResourceUtils::tryParseColor("#ff0000"))
                                           .build())
             .build();
@@ -147,7 +147,7 @@
     ReferenceLinker linker;
     ASSERT_TRUE(linker.consume(context.get(), table.get()));
 
-    Style* style = test::getValue<Style>(table.get(), "@com.app.test:style/Theme");
+    Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme");
     ASSERT_NE(style, nullptr);
     ASSERT_EQ(1u, style->entries.size());
     AAPT_ASSERT_TRUE(style->entries.front().key.id);
@@ -157,8 +157,8 @@
 TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("com.app.test", 0x7f)
-            .addReference("@com.app.test:string/foo", ResourceId(0x7f020000),
-                          "@android:string/hidden")
+            .addReference("com.app.test:string/foo", ResourceId(0x7f020000),
+                          "android:string/hidden")
             .build();
 
     std::unique_ptr<IAaptContext> context = test::ContextBuilder()
@@ -167,7 +167,7 @@
             .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" })
             .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
             .addSymbolSource(test::StaticSymbolSourceBuilder()
-                                     .addSymbol("@android:string/hidden", ResourceId(0x01040034))
+                                     .addSymbol("android:string/hidden", ResourceId(0x01040034))
                                      .build())
             .build();
 
@@ -178,8 +178,8 @@
 TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("com.app.test", 0x7f)
-            .addReference("@com.app.test:string/foo", ResourceId(0x7f020000),
-                          "@com.app.lib:string/hidden")
+            .addReference("com.app.test:string/foo", ResourceId(0x7f020000),
+                          "com.app.lib:string/hidden")
             .build();
 
     std::unique_ptr<IAaptContext> context = test::ContextBuilder()
@@ -188,7 +188,7 @@
             .setNameManglerPolicy(NameManglerPolicy{ "com.app.test", { "com.app.lib" } })
             .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
             .addSymbolSource(test::StaticSymbolSourceBuilder()
-                                     .addSymbol("@com.app.test:string/com.app.lib$hidden",
+                                     .addSymbol("com.app.test:string/com.app.lib$hidden",
                                                 ResourceId(0x7f040034))
                                      .build())
 
@@ -201,8 +201,8 @@
 TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("com.app.test", 0x7f)
-            .addValue("@com.app.test:style/Theme", test::StyleBuilder()
-                    .addItem("@android:attr/hidden", ResourceUtils::tryParseColor("#ff00ff"))
+            .addValue("com.app.test:style/Theme", test::StyleBuilder()
+                    .addItem("android:attr/hidden", ResourceUtils::tryParseColor("#ff00ff"))
                     .build())
             .build();
 
@@ -212,7 +212,7 @@
             .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" })
             .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get()))
             .addSymbolSource(test::StaticSymbolSourceBuilder()
-                                     .addSymbol("@android:attr/hidden", ResourceId(0x01010001),
+                                     .addSymbol("android:attr/hidden", ResourceId(0x01010001),
                                                 test::AttributeBuilder()
                                                         .setTypeMask(
                                                                 android::ResTable_map::TYPE_COLOR)
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 1697217..300b56d 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -45,16 +45,16 @@
 TEST_F(TableMergerTest, SimpleMerge) {
     std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
             .setPackageId("com.app.a", 0x7f)
-            .addReference("@com.app.a:id/foo", "@com.app.a:id/bar")
-            .addReference("@com.app.a:id/bar", "@com.app.b:id/foo")
-            .addValue("@com.app.a:styleable/view", test::StyleableBuilder()
-                    .addItem("@com.app.b:id/foo")
+            .addReference("com.app.a:id/foo", "com.app.a:id/bar")
+            .addReference("com.app.a:id/bar", "com.app.b:id/foo")
+            .addValue("com.app.a:styleable/view", test::StyleableBuilder()
+                    .addItem("com.app.b:id/foo")
                     .build())
             .build();
 
     std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
             .setPackageId("com.app.b", 0x7f)
-            .addSimple("@com.app.b:id/foo")
+            .addSimple("com.app.b:id/foo")
             .build();
 
     ResourceTable finalTable;
@@ -67,15 +67,15 @@
     EXPECT_TRUE(merger.getMergedPackages().count("com.app.b") != 0);
 
     // Entries from com.app.a should not be mangled.
-    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("@com.app.a:id/foo")));
-    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("@com.app.a:id/bar")));
-    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("@com.app.a:styleable/view")));
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:id/foo")));
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:id/bar")));
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:styleable/view")));
 
     // The unmangled name should not be present.
-    AAPT_EXPECT_FALSE(finalTable.findResource(test::parseNameOrDie("@com.app.b:id/foo")));
+    AAPT_EXPECT_FALSE(finalTable.findResource(test::parseNameOrDie("com.app.b:id/foo")));
 
     // Look for the mangled name.
-    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("@com.app.a:id/com.app.b$foo")));
+    AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:id/com.app.b$foo")));
 }
 
 TEST_F(TableMergerTest, MergeFile) {
@@ -86,14 +86,14 @@
 
     ResourceFile fileDesc;
     fileDesc.config = test::parseConfigOrDie("hdpi-v4");
-    fileDesc.name = test::parseNameOrDie("@layout/main");
+    fileDesc.name = test::parseNameOrDie("layout/main");
     fileDesc.source = Source("res/layout-hdpi/main.xml");
     test::TestFile testFile("path/to/res/layout-hdpi/main.xml.flat");
 
     ASSERT_TRUE(merger.mergeFile(fileDesc, &testFile));
 
     FileReference* file = test::getValueForConfig<FileReference>(&finalTable,
-                                                                 "@com.app.a:layout/main",
+                                                                 "com.app.a:layout/main",
                                                                  test::parseConfigOrDie("hdpi-v4"));
     ASSERT_NE(nullptr, file);
     EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), *file->path);
@@ -106,7 +106,7 @@
     TableMerger merger(mContext.get(), &finalTable, tableMergerOptions);
 
     ResourceFile fileDesc;
-    fileDesc.name = test::parseNameOrDie("@xml/foo");
+    fileDesc.name = test::parseNameOrDie("xml/foo");
     test::TestFile fileA("path/to/fileA.xml.flat");
     test::TestFile fileB("path/to/fileB.xml.flat");
 
@@ -117,11 +117,11 @@
 TEST_F(TableMergerTest, MergeFileReferences) {
     std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
             .setPackageId("com.app.a", 0x7f)
-            .addFileReference("@com.app.a:xml/file", "res/xml/file.xml")
+            .addFileReference("com.app.a:xml/file", "res/xml/file.xml")
             .build();
     std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
             .setPackageId("com.app.b", 0x7f)
-            .addFileReference("@com.app.b:xml/file", "res/xml/file.xml")
+            .addFileReference("com.app.b:xml/file", "res/xml/file.xml")
             .build();
 
     ResourceTable finalTable;
@@ -132,11 +132,11 @@
     ASSERT_TRUE(merger.merge({}, tableA.get()));
     ASSERT_TRUE(merger.mergeAndMangle({}, "com.app.b", tableB.get(), &collection));
 
-    FileReference* f = test::getValue<FileReference>(&finalTable, "@com.app.a:xml/file");
+    FileReference* f = test::getValue<FileReference>(&finalTable, "com.app.a:xml/file");
     ASSERT_NE(f, nullptr);
     EXPECT_EQ(std::string("res/xml/file.xml"), *f->path);
 
-    f = test::getValue<FileReference>(&finalTable, "@com.app.a:xml/com.app.b$file");
+    f = test::getValue<FileReference>(&finalTable, "com.app.a:xml/com.app.b$file");
     ASSERT_NE(f, nullptr);
     EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path);
 }
@@ -144,11 +144,11 @@
 TEST_F(TableMergerTest, OverrideResourceWithOverlay) {
     std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder()
             .setPackageId("", 0x00)
-            .addValue("@bool/foo", ResourceUtils::tryParseBool("true"))
+            .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
             .build();
     std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder()
             .setPackageId("", 0x00)
-            .addValue("@bool/foo", ResourceUtils::tryParseBool("false"))
+            .addValue("bool/foo", ResourceUtils::tryParseBool("false"))
             .build();
 
     ResourceTable finalTable;
@@ -159,7 +159,7 @@
     ASSERT_TRUE(merger.merge({}, base.get()));
     ASSERT_TRUE(merger.mergeOverlay({}, overlay.get()));
 
-    BinaryPrimitive* foo = test::getValue<BinaryPrimitive>(&finalTable, "@com.app.a:bool/foo");
+    BinaryPrimitive* foo = test::getValue<BinaryPrimitive>(&finalTable, "com.app.a:bool/foo");
     ASSERT_NE(nullptr, foo);
     EXPECT_EQ(0x0u, foo->value.data);
 }
@@ -167,11 +167,11 @@
 TEST_F(TableMergerTest, MergeAddResourceFromOverlay) {
     std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder()
             .setPackageId("", 0x7f)
-            .setSymbolState("@bool/foo", {}, SymbolState::kUndefined)
+            .setSymbolState("bool/foo", {}, SymbolState::kUndefined)
             .build();
     std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
             .setPackageId("", 0x7f)
-            .addValue("@bool/foo", ResourceUtils::tryParseBool("true"))
+            .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
             .build();
 
     ResourceTable finalTable;
@@ -187,7 +187,7 @@
             .build();
     std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
             .setPackageId("", 0x7f)
-            .addValue("@bool/foo", ResourceUtils::tryParseBool("true"))
+            .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
             .build();
 
     ResourceTable finalTable;
@@ -205,7 +205,7 @@
             .build();
     std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder()
             .setPackageId("", 0x7f)
-            .addValue("@bool/foo", ResourceUtils::tryParseBool("true"))
+            .addValue("bool/foo", ResourceUtils::tryParseBool("true"))
             .build();
 
     ResourceTable finalTable;
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index d48de42..51eb62c 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -27,40 +27,40 @@
                 .setNameManglerPolicy(
                         NameManglerPolicy{ "com.app.test", { "com.android.support" } })
                 .addSymbolSource(test::StaticSymbolSourceBuilder()
-                        .addPublicSymbol("@android:attr/layout_width", ResourceId(0x01010000),
+                        .addPublicSymbol("android:attr/layout_width", ResourceId(0x01010000),
                                    test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_ENUM |
                                                      android::ResTable_map::TYPE_DIMENSION)
                                         .addItem("match_parent", 0xffffffff)
                                         .build())
-                        .addPublicSymbol("@android:attr/background", ResourceId(0x01010001),
+                        .addPublicSymbol("android:attr/background", ResourceId(0x01010001),
                                    test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
-                        .addPublicSymbol("@android:attr/attr", ResourceId(0x01010002),
+                        .addPublicSymbol("android:attr/attr", ResourceId(0x01010002),
                                    test::AttributeBuilder().build())
-                        .addPublicSymbol("@android:attr/text", ResourceId(0x01010003),
+                        .addPublicSymbol("android:attr/text", ResourceId(0x01010003),
                                    test::AttributeBuilder()
                                         .setTypeMask(android::ResTable_map::TYPE_STRING)
                                         .build())
 
                          // Add one real symbol that was introduces in v21
-                        .addPublicSymbol("@android:attr/colorAccent", ResourceId(0x01010435),
+                        .addPublicSymbol("android:attr/colorAccent", ResourceId(0x01010435),
                                    test::AttributeBuilder().build())
 
                         // Private symbol.
-                        .addSymbol("@android:color/hidden", ResourceId(0x01020001))
+                        .addSymbol("android:color/hidden", ResourceId(0x01020001))
 
-                        .addPublicSymbol("@android:id/id", ResourceId(0x01030000))
-                        .addSymbol("@com.app.test:id/id", ResourceId(0x7f030000))
-                        .addSymbol("@com.app.test:color/green", ResourceId(0x7f020000))
-                        .addSymbol("@com.app.test:color/red", ResourceId(0x7f020001))
-                        .addSymbol("@com.app.test:attr/colorAccent", ResourceId(0x7f010000),
+                        .addPublicSymbol("android:id/id", ResourceId(0x01030000))
+                        .addSymbol("com.app.test:id/id", ResourceId(0x7f030000))
+                        .addSymbol("com.app.test:color/green", ResourceId(0x7f020000))
+                        .addSymbol("com.app.test:color/red", ResourceId(0x7f020001))
+                        .addSymbol("com.app.test:attr/colorAccent", ResourceId(0x7f010000),
                                    test::AttributeBuilder()
                                        .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
-                        .addPublicSymbol("@com.app.test:attr/com.android.support$colorAccent",
+                        .addPublicSymbol("com.app.test:attr/com.android.support$colorAccent",
                                    ResourceId(0x7f010001), test::AttributeBuilder()
                                        .setTypeMask(android::ResTable_map::TYPE_COLOR).build())
-                        .addPublicSymbol("@com.app.test:attr/attr", ResourceId(0x7f010002),
+                        .addPublicSymbol("com.app.test:attr/attr", ResourceId(0x7f010002),
                                    test::AttributeBuilder().build())
                         .build())
                 .build();
@@ -101,7 +101,7 @@
     Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get());
     ASSERT_NE(ref, nullptr);
     AAPT_ASSERT_TRUE(ref->name);
-    EXPECT_EQ(ref->name.value(), test::parseNameOrDie("@color/green")); // Make sure the name
+    EXPECT_EQ(ref->name.value(), test::parseNameOrDie("color/green")); // Make sure the name
                                                                         // didn't change.
     AAPT_ASSERT_TRUE(ref->id);
     EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000));
diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp
index 6866974..1626352 100644
--- a/tools/aapt2/process/SymbolTable_test.cpp
+++ b/tools/aapt2/process/SymbolTable_test.cpp
@@ -21,31 +21,31 @@
 
 TEST(ResourceTableSymbolSourceTest, FindSymbols) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
-            .addSimple("@android:id/foo", ResourceId(0x01020000))
-            .addSimple("@android:id/bar")
-            .addValue("@android:attr/foo", ResourceId(0x01010000),
+            .addSimple("android:id/foo", ResourceId(0x01020000))
+            .addSimple("android:id/bar")
+            .addValue("android:attr/foo", ResourceId(0x01010000),
                       test::AttributeBuilder().build())
             .build();
 
     ResourceTableSymbolSource symbolSource(table.get());
-    EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie("@android:id/foo")));
-    EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie("@android:id/bar")));
+    EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie("android:id/foo")));
+    EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie("android:id/bar")));
 
     std::unique_ptr<SymbolTable::Symbol> s = symbolSource.findByName(
-            test::parseNameOrDie("@android:attr/foo"));
+            test::parseNameOrDie("android:attr/foo"));
     ASSERT_NE(nullptr, s);
     EXPECT_NE(nullptr, s->attribute);
 }
 
 TEST(ResourceTableSymbolSourceTest, FindPrivateAttrSymbol) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
-            .addValue("@android:^attr-private/foo", ResourceId(0x01010000),
+            .addValue("android:^attr-private/foo", ResourceId(0x01010000),
                       test::AttributeBuilder().build())
             .build();
 
     ResourceTableSymbolSource symbolSource(table.get());
     std::unique_ptr<SymbolTable::Symbol> s = symbolSource.findByName(
-                test::parseNameOrDie("@android:attr/foo"));
+                test::parseNameOrDie("android:attr/foo"));
     ASSERT_NE(nullptr, s);
     EXPECT_NE(nullptr, s->attribute);
 }
diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp
index 78b32f7..af1b011 100644
--- a/tools/aapt2/proto/TableProtoSerializer_test.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp
@@ -24,36 +24,36 @@
     std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
             .setPackageId("com.app.a", 0x7f)
-            .addFileReference("@com.app.a:layout/main", ResourceId(0x7f020000),
+            .addFileReference("com.app.a:layout/main", ResourceId(0x7f020000),
                               "res/layout/main.xml")
-            .addReference("@com.app.a:layout/other", ResourceId(0x7f020001),
-                          "@com.app.a:layout/main")
-            .addString("@com.app.a:string/text", {}, "hi")
-            .addValue("@com.app.a:id/foo", {}, util::make_unique<Id>())
+            .addReference("com.app.a:layout/other", ResourceId(0x7f020001),
+                          "com.app.a:layout/main")
+            .addString("com.app.a:string/text", {}, "hi")
+            .addValue("com.app.a:id/foo", {}, util::make_unique<Id>())
             .build();
 
     Symbol publicSymbol;
     publicSymbol.state = SymbolState::kPublic;
-    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie("@com.app.a:layout/main"),
+    ASSERT_TRUE(table->setSymbolState(test::parseNameOrDie("com.app.a:layout/main"),
                                       ResourceId(0x7f020000),
                                       publicSymbol, context->getDiagnostics()));
 
-    Id* id = test::getValue<Id>(table.get(), "@com.app.a:id/foo");
+    Id* id = test::getValue<Id>(table.get(), "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("one"));
-    ASSERT_TRUE(table->addResource(test::parseNameOrDie("@com.app.a:plurals/hey"),
+    ASSERT_TRUE(table->addResource(test::parseNameOrDie("com.app.a:plurals/hey"),
                                    ConfigDescription{}, {}, std::move(plural),
                                    context->getDiagnostics()));
 
     // Make a resource with different products.
-    ASSERT_TRUE(table->addResource(test::parseNameOrDie("@com.app.a:integer/one"),
+    ASSERT_TRUE(table->addResource(test::parseNameOrDie("com.app.a:integer/one"),
                                    test::parseConfigOrDie("land"), {},
                                    test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 123u),
                                    context->getDiagnostics()));
-    ASSERT_TRUE(table->addResource(test::parseNameOrDie("@com.app.a:integer/one"),
+    ASSERT_TRUE(table->addResource(test::parseNameOrDie("com.app.a:integer/one"),
                                        test::parseConfigOrDie("land"), "tablet",
                                        test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 321u),
                                        context->getDiagnostics()));
@@ -62,9 +62,9 @@
     // The reference should point to a resource outside of this table to test that both
     // name and id get serialized.
     Reference expectedRef;
-    expectedRef.name = test::parseNameOrDie("@android:layout/main");
+    expectedRef.name = test::parseNameOrDie("android:layout/main");
     expectedRef.id = ResourceId(0x01020000);
-    ASSERT_TRUE(table->addResource(test::parseNameOrDie("@com.app.a:layout/abc"),
+    ASSERT_TRUE(table->addResource(test::parseNameOrDie("com.app.a:layout/abc"),
                                    ConfigDescription::defaultConfig(), {},
                                    util::make_unique<Reference>(expectedRef),
                                    context->getDiagnostics()));
@@ -77,28 +77,28 @@
                                                                      context->getDiagnostics());
     ASSERT_NE(nullptr, newTable);
 
-    Id* newId = test::getValue<Id>(newTable.get(), "@com.app.a:id/foo");
+    Id* newId = test::getValue<Id>(newTable.get(), "com.app.a:id/foo");
     ASSERT_NE(nullptr, newId);
     EXPECT_EQ(id->isWeak(), newId->isWeak());
 
     Maybe<ResourceTable::SearchResult> result = newTable->findResource(
-            test::parseNameOrDie("@com.app.a:layout/main"));
+            test::parseNameOrDie("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);
 
     // Find the product-dependent values
     BinaryPrimitive* prim = test::getValueForConfigAndProduct<BinaryPrimitive>(
-            newTable.get(), "@com.app.a:integer/one", test::parseConfigOrDie("land"), "");
+            newTable.get(), "com.app.a:integer/one", test::parseConfigOrDie("land"), "");
     ASSERT_NE(nullptr, prim);
     EXPECT_EQ(123u, prim->value.data);
 
     prim = test::getValueForConfigAndProduct<BinaryPrimitive>(
-            newTable.get(), "@com.app.a:integer/one", test::parseConfigOrDie("land"), "tablet");
+            newTable.get(), "com.app.a:integer/one", test::parseConfigOrDie("land"), "tablet");
     ASSERT_NE(nullptr, prim);
     EXPECT_EQ(321u, prim->value.data);
 
-    Reference* actualRef = test::getValue<Reference>(newTable.get(), "@com.app.a:layout/abc");
+    Reference* actualRef = test::getValue<Reference>(newTable.get(), "com.app.a:layout/abc");
     ASSERT_NE(nullptr, actualRef);
     AAPT_ASSERT_TRUE(actualRef->name);
     AAPT_ASSERT_TRUE(actualRef->id);
@@ -111,9 +111,9 @@
 
     ResourceFile f;
     f.config = test::parseConfigOrDie("hdpi-v9");
-    f.name = test::parseNameOrDie("@com.app.a:layout/main");
+    f.name = test::parseNameOrDie("com.app.a:layout/main");
     f.source.path = "res/layout-hdpi-v9/main.xml";
-    f.exportedSymbols.push_back(SourcedResourceName{ test::parseNameOrDie("@+id/unchecked"), 23u });
+    f.exportedSymbols.push_back(SourcedResourceName{ test::parseNameOrDie("id/unchecked"), 23u });
 
     const std::string expectedData = "1234";
 
@@ -141,7 +141,7 @@
     EXPECT_EQ(0u, reinterpret_cast<uintptr_t>(inFileStream.data()) & 0x03);
 
     ASSERT_EQ(1u, file->exportedSymbols.size());
-    EXPECT_EQ(test::parseNameOrDie("@+id/unchecked"), file->exportedSymbols[0].name);
+    EXPECT_EQ(test::parseNameOrDie("id/unchecked"), file->exportedSymbols[0].name);
 }
 
 TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) {
diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md
new file mode 100644
index 0000000..95c0173
--- /dev/null
+++ b/tools/aapt2/readme.md
@@ -0,0 +1,18 @@
+# Android Asset Packaging Tool 2.0 (AAPT2) release notes
+
+## Version 2.0
+### `aapt2 compile ...`
+- Pseudo-localization: generates pseudolocalized versions of default strings when the
+  `--pseudo-localize` option is specified.
+- Legacy mode: treats some class of errors as warnings in order to be more compatible
+  with AAPT when `--legacy` is specified.
+- Compile directory: treats the input file as a directory when `--dir` is
+  specified. This will emit a zip of compiled files, one for each file in the directory.
+  The directory must follow the Android resource directory structure
+  (res/values-[qualifiers]/file.ext).
+
+### `aapt2 link ...`
+- Automatic attribute versioning: adds version qualifiers to resources that use attributes
+  introduced in a later SDK level. This can be disabled with `--no-auto-version`.
+- Min SDK resource filtering: removes resources that can't possibly be selected at runtime due
+  to the application's minimum supported SDK level.
diff --git a/tools/aapt2/split/TableSplitter_test.cpp b/tools/aapt2/split/TableSplitter_test.cpp
index a6dfd62..bad02a5 100644
--- a/tools/aapt2/split/TableSplitter_test.cpp
+++ b/tools/aapt2/split/TableSplitter_test.cpp
@@ -21,15 +21,15 @@
 
 TEST(TableSplitterTest, NoSplitPreferredDensity) {
     std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
-            .addFileReference("@android:drawable/icon", "res/drawable-mdpi/icon.png",
+            .addFileReference("android:drawable/icon", "res/drawable-mdpi/icon.png",
                               test::parseConfigOrDie("mdpi"))
-            .addFileReference("@android:drawable/icon", "res/drawable-hdpi/icon.png",
+            .addFileReference("android:drawable/icon", "res/drawable-hdpi/icon.png",
                               test::parseConfigOrDie("hdpi"))
-            .addFileReference("@android:drawable/icon", "res/drawable-xhdpi/icon.png",
+            .addFileReference("android:drawable/icon", "res/drawable-xhdpi/icon.png",
                               test::parseConfigOrDie("xhdpi"))
-            .addFileReference("@android:drawable/icon", "res/drawable-xxhdpi/icon.png",
+            .addFileReference("android:drawable/icon", "res/drawable-xxhdpi/icon.png",
                               test::parseConfigOrDie("xxhdpi"))
-            .addSimple("@android:string/one")
+            .addSimple("android:string/one")
             .build();
 
     TableSplitterOptions options;
@@ -38,24 +38,24 @@
     splitter.splitTable(table.get());
 
     EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
-                                                              "@android:drawable/icon",
+                                                              "android:drawable/icon",
                                                               test::parseConfigOrDie("mdpi")));
     EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
-                                                              "@android:drawable/icon",
+                                                              "android:drawable/icon",
                                                               test::parseConfigOrDie("hdpi")));
     EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(table.get(),
-                                                              "@android:drawable/icon",
+                                                              "android:drawable/icon",
                                                               test::parseConfigOrDie("xhdpi")));
     EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(),
-                                                              "@android:drawable/icon",
+                                                              "android:drawable/icon",
                                                               test::parseConfigOrDie("xxhdpi")));
-    EXPECT_NE(nullptr, test::getValue<Id>(table.get(), "@android:string/one"));
+    EXPECT_NE(nullptr, test::getValue<Id>(table.get(), "android:string/one"));
 }
 
 TEST(TableSplitterTest, SplitTableByConfigAndDensity) {
     ResourceTable table;
 
-    const ResourceName foo = test::parseNameOrDie("@android:string/foo");
+    const ResourceName foo = test::parseNameOrDie("android:string/foo");
     ASSERT_TRUE(table.addResource(foo, test::parseConfigOrDie("land-hdpi"), {},
                                   util::make_unique<Id>(),
                                   test::getDiagnostics()));
@@ -79,25 +79,25 @@
     ResourceTable* splitTwo = splitter.getSplits()[1].get();
 
     // Since a split was defined, all densities should be gone from base.
-    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "@android:string/foo",
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo",
                                                    test::parseConfigOrDie("land-hdpi")));
-    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "@android:string/foo",
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo",
                                                    test::parseConfigOrDie("land-xhdpi")));
-    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "@android:string/foo",
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo",
                                                    test::parseConfigOrDie("land-xxhdpi")));
 
-    EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitOne, "@android:string/foo",
+    EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitOne, "android:string/foo",
                                                    test::parseConfigOrDie("land-hdpi")));
-    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, "@android:string/foo",
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, "android:string/foo",
                                                    test::parseConfigOrDie("land-xhdpi")));
-    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, "@android:string/foo",
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitOne, "android:string/foo",
                                                    test::parseConfigOrDie("land-xxhdpi")));
 
-    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, "@android:string/foo",
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, "android:string/foo",
                                                    test::parseConfigOrDie("land-hdpi")));
-    EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitTwo, "@android:string/foo",
+    EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitTwo, "android:string/foo",
                                                    test::parseConfigOrDie("land-xhdpi")));
-    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, "@android:string/foo",
+    EXPECT_EQ(nullptr, test::getValueForConfig<Id>(splitTwo, "android:string/foo",
                                                    test::parseConfigOrDie("land-xxhdpi")));
 }
 
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index b2eaba6..624a71a 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -64,7 +64,7 @@
 
 inline ResourceName parseNameOrDie(const StringPiece& str) {
     ResourceNameRef ref;
-    bool result = ResourceUtils::tryParseReference(str, &ref);
+    bool result = ResourceUtils::parseResourceName(str, &ref);
     assert(result && "invalid resource name");
     return ref.toResourceName();
 }