Merge "Add perf tests for PendingIntent"
diff --git a/api/current.txt b/api/current.txt
index 3c3cfcc..9669cfd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6493,7 +6493,6 @@
     method public boolean isNetworkLoggingEnabled(android.content.ComponentName);
     method public boolean isOverrideApnEnabled(android.content.ComponentName);
     method public boolean isPackageSuspended(android.content.ComponentName, java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public boolean isPrintingEnabled();
     method public boolean isProfileOwnerApp(java.lang.String);
     method public boolean isProvisioningAllowed(java.lang.String);
     method public boolean isResetPasswordTokenActive(android.content.ComponentName);
@@ -6566,7 +6565,6 @@
     method public boolean setPermittedAccessibilityServices(android.content.ComponentName, java.util.List<java.lang.String>);
     method public boolean setPermittedCrossProfileNotificationListeners(android.content.ComponentName, java.util.List<java.lang.String>);
     method public boolean setPermittedInputMethods(android.content.ComponentName, java.util.List<java.lang.String>);
-    method public void setPrintingEnabled(android.content.ComponentName, boolean);
     method public void setProfileEnabled(android.content.ComponentName);
     method public void setProfileName(android.content.ComponentName, java.lang.String);
     method public void setRecommendedGlobalProxy(android.content.ComponentName, android.net.ProxyInfo);
@@ -33140,6 +33138,7 @@
     field public static final java.lang.String DISALLOW_NETWORK_RESET = "no_network_reset";
     field public static final java.lang.String DISALLOW_OUTGOING_BEAM = "no_outgoing_beam";
     field public static final java.lang.String DISALLOW_OUTGOING_CALLS = "no_outgoing_calls";
+    field public static final java.lang.String DISALLOW_PRINTING = "no_printing";
     field public static final java.lang.String DISALLOW_REMOVE_MANAGED_PROFILE = "no_remove_managed_profile";
     field public static final java.lang.String DISALLOW_REMOVE_USER = "no_remove_user";
     field public static final java.lang.String DISALLOW_SAFE_BOOT = "no_safe_boot";
@@ -48147,8 +48146,6 @@
     method public void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
     method public final void offsetDescendantRectToMyCoords(android.view.View, android.graphics.Rect);
     method public final void offsetRectIntoDescendantCoords(android.view.View, android.graphics.Rect);
-    method protected void onDebugDraw(android.graphics.Canvas);
-    method protected void onDebugDrawMargins(android.graphics.Canvas, android.graphics.Paint);
     method public boolean onInterceptHoverEvent(android.view.MotionEvent);
     method public boolean onInterceptTouchEvent(android.view.MotionEvent);
     method protected abstract void onLayout(boolean, int, int, int, int);
@@ -48222,7 +48219,6 @@
     ctor public ViewGroup.LayoutParams(android.content.Context, android.util.AttributeSet);
     ctor public ViewGroup.LayoutParams(int, int);
     ctor public ViewGroup.LayoutParams(android.view.ViewGroup.LayoutParams);
-    method public void onDebugDraw(android.view.View, android.graphics.Canvas, android.graphics.Paint);
     method public void resolveLayoutDirection(int);
     method protected void setBaseAttributes(android.content.res.TypedArray, int, int);
     field public static final deprecated int FILL_PARENT = -1; // 0xffffffff
diff --git a/api/test-current.txt b/api/test-current.txt
index c30c056..4cfe401 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1078,6 +1078,12 @@
     field public static final int MODE_SPINNER = 1; // 0x1
   }
 
+  public final class Magnifier {
+    method public android.graphics.Bitmap getContent();
+    method public static android.graphics.PointF getMagnifierDefaultSize();
+    method public android.graphics.Rect getWindowPositionOnScreen();
+  }
+
   public class NumberPicker extends android.widget.LinearLayout {
     method public java.lang.CharSequence getDisplayedValueForCurrentSelection();
   }
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index 1d5ab59..654036e 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -46,6 +46,7 @@
 checkIncidentPermissions(const IncidentReportArgs& args)
 {
     uid_t callingUid = IPCThreadState::self()->getCallingUid();
+    pid_t callingPid = IPCThreadState::self()->getCallingPid();
     if (callingUid == AID_ROOT || callingUid == AID_SHELL) {
         // root doesn't have permission.DUMP if don't do this!
         return Status::ok();
@@ -54,13 +55,13 @@
     // checking calling permission.
     if (!checkCallingPermission(DUMP_PERMISSION)) {
         ALOGW("Calling pid %d and uid %d does not have permission: android.permission.DUMP",
-                IPCThreadState::self()->getCallingPid(), callingUid);
+                callingPid, callingUid);
         return Status::fromExceptionCode(Status::EX_SECURITY,
                 "Calling process does not have permission: android.permission.DUMP");
     }
     if (!checkCallingPermission(USAGE_STATS_PERMISSION)) {
         ALOGW("Calling pid %d and uid %d does not have permission: android.permission.USAGE_STATS",
-                IPCThreadState::self()->getCallingPid(), callingUid);
+                callingPid, callingUid);
         return Status::fromExceptionCode(Status::EX_SECURITY,
                 "Calling process does not have permission: android.permission.USAGE_STATS");
     }
@@ -68,13 +69,17 @@
     // checking calling request uid permission.
     switch (args.dest()) {
         case DEST_LOCAL:
-            if (callingUid != AID_SHELL || callingUid != AID_ROOT) {
+            if (callingUid != AID_SHELL && callingUid != AID_ROOT) {
+                ALOGW("Calling pid %d and uid %d does not have permission to get local data.",
+                        callingPid, callingUid);
                 return Status::fromExceptionCode(Status::EX_SECURITY,
                     "Calling process does not have permission to get local data.");
             }
         case DEST_EXPLICIT:
-            if (callingUid != AID_SHELL || callingUid != AID_ROOT ||
-                callingUid != AID_STATSD || callingUid != AID_SYSTEM) {
+            if (callingUid != AID_SHELL && callingUid != AID_ROOT &&
+                callingUid != AID_STATSD && callingUid != AID_SYSTEM) {
+                ALOGW("Calling pid %d and uid %d does not have permission to get explicit data.",
+                        callingPid, callingUid);
                 return Status::fromExceptionCode(Status::EX_SECURITY,
                     "Calling process does not have permission to get explicit data.");
             }
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index b0019ac..b46c5c1 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -65,6 +65,7 @@
     src/storage/StorageManager.cpp \
     src/StatsLogProcessor.cpp \
     src/StatsService.cpp \
+    src/subscriber/IncidentdReporter.cpp \
     src/subscriber/SubscriberReporter.cpp \
     src/HashableDimensionKey.cpp \
     src/guardrail/MemoryLeakTrackUtil.cpp \
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index a4066aa..e610fd7 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -146,6 +146,12 @@
         return;
     }
 
+    long curTime = time(nullptr);
+    if (curTime - mLastPullerCacheClearTimeSec > StatsdStats::kPullerCacheClearIntervalSec) {
+        mStatsPullerManager.ClearPullerCacheIfNecessary(curTime);
+        mLastPullerCacheClearTimeSec = curTime;
+    }
+
     if (event->GetTagId() != android::util::ISOLATED_UID_CHANGED) {
         // Map the isolated uid to host uid if necessary.
         mapIsolatedUidToHostUidIfNecessaryLocked(event);
@@ -290,6 +296,10 @@
     StatsdStats::getInstance().noteConfigRemoved(key);
 
     mLastBroadcastTimes.erase(key);
+
+    if (mMetricsManagers.empty()) {
+        mStatsPullerManager.ForceClearPullerCache();
+    }
 }
 
 void StatsLogProcessor::flushIfNecessaryLocked(
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 17741a8..8bbcd75 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -21,6 +21,7 @@
 #include "logd/LogReader.h"
 #include "metrics/MetricsManager.h"
 #include "packages/UidMap.h"
+#include "external/StatsPullerManager.h"
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
 
@@ -75,6 +76,8 @@
 
     sp<UidMap> mUidMap;  // Reference to the UidMap to lookup app name and version for each uid.
 
+    StatsPullerManager mStatsPullerManager;
+
     sp<AnomalyMonitor> mAnomalyMonitor;
 
     void onDumpReportLocked(const ConfigKey& key, vector<uint8_t>* outData);
@@ -96,6 +99,8 @@
 
     const long mTimeBaseSec;
 
+    long mLastPullerCacheClearTimeSec = 0;
+
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitByteSize);
     FRIEND_TEST(StatsLogProcessorTest, TestRateLimitBroadcast);
     FRIEND_TEST(StatsLogProcessorTest, TestDropWhenByteSizeTooLarge);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 32da94f..3efe9b1 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -240,6 +240,10 @@
         if (!args[0].compare(String8("log-app-hook"))) {
             return cmd_log_app_hook(out, args);
         }
+
+        if (!args[0].compare(String8("clear-puller-cache"))) {
+            return cmd_clear_puller_cache(out);
+        }
     }
 
     print_cmd_help(out);
@@ -320,6 +324,10 @@
     fprintf(out, "\n");
     fprintf(out, "usage: adb shell cmd stats print-stats\n");
     fprintf(out, "  Prints some basic stats.\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
+    fprintf(out, "usage: adb shell cmd stats clear-puller-cache\n");
+    fprintf(out, "  Clear cached puller data.\n");
 }
 
 status_t StatsService::cmd_trigger_broadcast(FILE* out, Vector<String8>& args) {
@@ -602,9 +610,15 @@
 }
 
 status_t StatsService::cmd_clear_puller_cache(FILE* out) {
-    mStatsPullerManager.ClearPullerCache();
-    fprintf(out, "Puller cached data removed!\n");
-    return NO_ERROR;
+    IPCThreadState* ipc = IPCThreadState::self();
+    VLOG("StatsService::cmd_clear_puller_cache with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
+    if (checkCallingPermission(String16(kPermissionDump))) {
+        int cleared = mStatsPullerManager.ForceClearPullerCache();
+        fprintf(out, "Puller removed %d cached data!\n", cleared);
+        return NO_ERROR;
+    } else {
+        return PERMISSION_DENIED;
+    }
 }
 
 Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int64_t>& version,
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index 79b7d9c..7c5e45e 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -19,13 +19,11 @@
 
 #include "AnomalyTracker.h"
 #include "external/Perfetto.h"
-#include "guardrail/StatsdStats.h"
 #include "frameworks/base/libs/incident/proto/android/os/header.pb.h"
+#include "guardrail/StatsdStats.h"
+#include "subscriber/IncidentdReporter.h"
 #include "subscriber/SubscriberReporter.h"
 
-#include <android/os/IIncidentManager.h>
-#include <android/os/IncidentReportArgs.h>
-#include <binder/IServiceManager.h>
 #include <statslog.h>
 #include <time.h>
 
@@ -38,15 +36,14 @@
     : mAlert(alert), mConfigKey(configKey), mNumOfPastBuckets(mAlert.num_buckets() - 1) {
     VLOG("AnomalyTracker() called");
     if (mAlert.num_buckets() <= 0) {
-        ALOGE("Cannot create AnomalyTracker with %lld buckets",
-              (long long)mAlert.num_buckets());
+        ALOGE("Cannot create AnomalyTracker with %lld buckets", (long long)mAlert.num_buckets());
         return;
     }
     if (!mAlert.has_trigger_if_sum_gt()) {
         ALOGE("Cannot create AnomalyTracker without threshold");
         return;
     }
-    resetStorage(); // initialization
+    resetStorage();  // initialization
 }
 
 AnomalyTracker::~AnomalyTracker() {
@@ -169,8 +166,8 @@
         // TODO: This creates a needless 0 entry in mSumOverPastBuckets. Fix this.
         addPastBucket(key, 0, currentBucketNum - 1);
     }
-    return mAlert.has_trigger_if_sum_gt()
-            && getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
+    return mAlert.has_trigger_if_sum_gt() &&
+           getSumOverPastBuckets(key) + currentBucketValue > mAlert.trigger_if_sum_gt();
 }
 
 void AnomalyTracker::declareAnomaly(const uint64_t& timestampNs, const MetricDimensionKey& key) {
@@ -186,7 +183,7 @@
 
     if (!mSubscriptions.empty()) {
         if (mAlert.has_id()) {
-            ALOGI("An anomaly (%llu) has occurred! Informing subscribers.",mAlert.id());
+            ALOGI("An anomaly (%llu) has occurred! Informing subscribers.", mAlert.id());
             informSubscribers(key);
         } else {
             ALOGI("An anomaly (with no id) has occurred! Not informing any subscribers.");
@@ -231,44 +228,26 @@
         return;
     }
 
-    std::set<int> incidentdSections;
-
     for (const Subscription& subscription : mSubscriptions) {
         switch (subscription.subscriber_information_case()) {
             case Subscription::SubscriberInformationCase::kIncidentdDetails:
-                for (int i = 0; i < subscription.incidentd_details().section_size(); i++) {
-                    incidentdSections.insert(subscription.incidentd_details().section(i));
+                if (!GenerateIncidentReport(subscription.incidentd_details(), mAlert, mConfigKey)) {
+                    ALOGW("Failed to generate incident report.");
                 }
                 break;
             case Subscription::SubscriberInformationCase::kPerfettoDetails:
-                CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details());
+                if (!CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details())) {
+                    ALOGW("Failed to generate prefetto traces.");
+                }
                 break;
             case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
-                SubscriberReporter::getInstance()
-                        .alertBroadcastSubscriber(mConfigKey, subscription, key);
+                SubscriberReporter::getInstance().alertBroadcastSubscriber(mConfigKey, subscription,
+                                                                           key);
                 break;
             default:
                 break;
         }
     }
-    if (!incidentdSections.empty()) {
-        sp<IIncidentManager> service = interface_cast<IIncidentManager>(
-                defaultServiceManager()->getService(android::String16("incident")));
-        if (service != NULL) {
-            IncidentReportArgs incidentReport;
-            for (const auto section : incidentdSections) {
-                incidentReport.addSection(section);
-            }
-            android::os::IncidentHeaderProto header;
-            header.set_alert_id(mAlert.id());
-            header.mutable_config_key()->set_uid(mConfigKey.GetUid());
-            header.mutable_config_key()->set_id(mConfigKey.GetId());
-            incidentReport.addHeader(header);
-            service->reportIncident(incidentReport);
-        } else {
-            ALOGW("Couldn't get the incident service.");
-        }
-    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index f01a97f..3be959d 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -16,22 +16,24 @@
 
 #pragma once
 
+#include <memory>  // unique_ptr
+
+#include <stdlib.h>
+
 #include <gtest/gtest_prod.h>
+#include <utils/RefBase.h>
+
 #include "AnomalyMonitor.h"
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // Alert
 #include "stats_util.h"  // HashableDimensionKey and DimToValMap
 
-#include <memory> // unique_ptr
-#include <stdlib.h>
-#include <utils/RefBase.h>
-
 namespace android {
 namespace os {
 namespace statsd {
 
-using std::unordered_map;
 using std::shared_ptr;
+using std::unordered_map;
 
 // Does NOT allow negative values.
 class AnomalyTracker : public virtual RefBase {
@@ -60,12 +62,11 @@
 
     // Detects the alert and informs the incidentd when applicable.
     void detectAndDeclareAnomaly(const uint64_t& timestampNs, const int64_t& currBucketNum,
-                                 const MetricDimensionKey& key,
-                                 const int64_t& currentBucketValue);
+                                 const MetricDimensionKey& key, const int64_t& currentBucketValue);
 
     // Init the AnomalyMonitor which is shared across anomaly trackers.
     virtual void setAnomalyMonitor(const sp<AnomalyMonitor>& anomalyMonitor) {
-        return; // Base AnomalyTracker class has no need for the AnomalyMonitor.
+        return;  // Base AnomalyTracker class has no need for the AnomalyMonitor.
     }
 
     // Helper function to return the sum value of past buckets at given dimension.
@@ -92,9 +93,10 @@
 
     // Declares an anomaly for each alarm in firedAlarms that belongs to this AnomalyTracker,
     // and removes it from firedAlarms. Does NOT remove the alarm from the AnomalyMonitor.
-    virtual void informAlarmsFired(const uint64_t& timestampNs,
+    virtual void informAlarmsFired(
+            const uint64_t& timestampNs,
             unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
-        return; // The base AnomalyTracker class doesn't have alarms.
+        return;  // The base AnomalyTracker class doesn't have alarms.
     }
 
 protected:
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
index bbee9fa..e459f76 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.cpp
@@ -52,8 +52,7 @@
 }
 
 void DurationAnomalyTracker::startAlarm(const MetricDimensionKey& dimensionKey,
-                                const uint64_t& timestampNs) {
-
+                                        const uint64_t& timestampNs) {
     uint32_t timestampSec = static_cast<uint32_t>(timestampNs / NS_PER_SEC);
     if (isInRefractoryPeriod(timestampNs, dimensionKey)) {
         VLOG("Skipping setting anomaly alarm since it'd fall in the refractory period");
@@ -86,15 +85,15 @@
     }
 }
 
-void DurationAnomalyTracker::informAlarmsFired(const uint64_t& timestampNs,
+void DurationAnomalyTracker::informAlarmsFired(
+        const uint64_t& timestampNs,
         unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) {
-
     if (firedAlarms.empty() || mAlarms.empty()) return;
     // Find the intersection of firedAlarms and mAlarms.
     // The for loop is inefficient, since it loops over all keys, but that's okay since it is very
     // seldomly called. The alternative would be having AnomalyAlarms store information about the
-    // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that is
-    // rarely ever called.
+    // DurationAnomalyTracker and key, but that's a lot of data overhead to speed up something that
+    // is rarely ever called.
     unordered_map<MetricDimensionKey, sp<const AnomalyAlarm>> matchedAlarms;
     for (const auto& kv : mAlarms) {
         if (firedAlarms.count(kv.second) > 0) {
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
index 052fdf57..ba687da 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -53,7 +53,8 @@
     // and removes it from firedAlarms.
     // Note that this will generally be called from a different thread from the other functions;
     // the caller is responsible for thread safety.
-    void informAlarmsFired(const uint64_t& timestampNs,
+    void informAlarmsFired(
+            const uint64_t& timestampNs,
             unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override;
 
 protected:
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index e58c535..6bca16f 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -1019,6 +1019,138 @@
 }
 
 /**
+ * Logs creation or removal of an isolated uid. Isolated uid's are temporary uid's to sandbox risky
+ * behavior in its own uid. However, the metrics of these isolated uid's almost always should be
+ * attributed back to the parent (host) uid. One example is Chrome.
+ *
+ * Logged from:
+ *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
+ */
+message IsolatedUidChanged {
+    // The host UID. Generally, we should attribute metrics from the isolated uid to the host uid.
+    optional int32 parent_uid = 1;
+
+    optional int32 isolated_uid = 2;
+
+    // We expect an isolated uid to be removed before if it's used for another parent uid.
+    enum Event {
+        REMOVED = 0;
+        CREATED = 1;
+    }
+    optional Event event = 3;
+}
+
+/*
+ * Logs the reception of an incoming network packet causing the main system to wake up for
+ * processing that packet. These events are notified by the kernel via Netlink NFLOG to Netd
+ * and processed by WakeupController.cpp.
+ */
+message PacketWakeupOccurred {
+    // The uid owning the socket into which the packet was delivered, or -1 if the packet was
+    // delivered nowhere.
+    optional int32 uid = 1;
+    // The interface name on which the packet was received.
+    optional string iface = 2;
+    // The ethertype value of the packet.
+    optional int32 ethertype = 3;
+    // String representation of the destination MAC address of the packet.
+    optional string destination_hardware_address = 4;
+    // String representation of the source address of the packet if this was an IP packet.
+    optional string source_ip = 5;
+    // String representation of the destination address of the packet if this was an IP packet.
+    optional string destination_ip = 6;
+    // The value of the protocol field if this was an IPv4 packet or the value of the Next Header
+    // field if this was an IPv6 packet. The range of possible values is the same for both IP
+    // families.
+    optional int32 ip_next_header = 7;
+    // The source port if this was a TCP or UDP packet.
+    optional int32 source_port = 8;
+    // The destination port if this was a TCP or UDP packet.
+    optional int32 destination_port = 9;
+}
+
+/*
+ * Logs the memory stats for an app on startup.
+ * Logged from:
+ *     frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ */
+message AppStartMemoryStateCaptured {
+    // The uid if available. -1 means not available.
+    optional int32 uid = 1;
+
+    // The process name.
+    optional string process_name = 2;
+
+    // The activity name.
+    optional string activity_name = 3;
+
+    // # of page-faults
+    optional int64 pgfault = 4;
+
+    // # of major page-faults
+    optional int64 pgmajfault = 5;
+
+    // RSS
+    optional int64 rss_in_bytes = 6;
+
+    // CACHE
+    optional int64 cache_in_bytes = 7;
+
+    // SWAP
+    optional int64 swap_in_bytes = 8;
+}
+
+/*
+ * Logs the change in Low Memory Killer Daemon (LMKD) state which is used as start/stop boundaries
+ * for LMK event.
+ * Logged from:
+ *      system/core/lmkd/lmkd.c
+ */
+message LmkStateChanged {
+    enum State {
+        UNKNOWN = 0;
+        START = 1;
+        STOP = 2;
+    }
+    optional State state = 1;
+}
+
+/*
+ * Logs the event when Low Memory Killer Daemon (LMKD) kills a process to reduce memory pressure.
+ * Logged from:
+ *      system/core/lmkd/lmkd.c
+ */
+message LmkKillOccurred {
+    // The uid if available. -1 means not available.
+    optional int32 uid = 1;
+
+    // The process name.
+    optional string process_name = 2;
+
+    // oom adj score.
+    optional int32 oom_score = 3;
+
+    // # of page-faults
+    optional int64 pgfault = 4;
+
+    // # of major page-faults
+    optional int64 pgmajfault = 5;
+
+    // RSS
+    optional int64 rss_in_bytes = 6;
+
+    // CACHE
+    optional int64 cache_in_bytes = 7;
+
+    // SWAP
+    optional int64 swap_in_bytes = 8;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Pulled atoms below this line //
+//////////////////////////////////////////////////////////////////////
+
+/**
  * Pulls bytes transferred via wifi (Sum of foreground and background usage).
  *
  * Pulled from:
@@ -1134,37 +1266,15 @@
  *   hardware/interfaces/power/1.1/types.hal
  */
 message SubsystemSleepState {
-    // Name should be in the format of XXX.YYY where XXX is subsystem name,
-    // YYY is corresponding voter name.
-    // If there are no voters, the format should just be XXX (with no dot).
-    // XXX and YYY should not contain a "." in it.
-    optional string name = 1;
+    // Subsystem name
+    optional string subsystem_name = 1;
+    // For PlatformLowPowerStats (hal 1.0), this is the voter name, which could be empty.
+    // For SubsystemLowPowerStats (hal 1.1), this is the sleep state name.
+    optional string subname = 2;
     // The number of times it entered, or voted for entering the sleep state
-    optional uint64 count = 2;
+    optional uint64 count = 3;
     // The length of time spent in, or spent voting for, the sleep state
-    optional uint64 timeMs = 3;
-}
-
-/**
- * Logs creation or removal of an isolated uid. Isolated uid's are temporary uid's to sandbox risky
- * behavior in its own uid. However, the metrics of these isolated uid's almost always should be
- * attributed back to the parent (host) uid. One example is Chrome.
- *
- * Logged from:
- *   frameworks/base/core/java/com/android/internal/os/BatteryStatsImpl.java
- */
-message IsolatedUidChanged {
-    // The host UID. Generally, we should attribute metrics from the isolated uid to the host uid.
-    optional int32 parent_uid = 1;
-
-    optional int32 isolated_uid = 2;
-
-    // We expect an isolated uid to be removed before if it's used for another parent uid.
-    enum Event {
-        REMOVED = 0;
-        CREATED = 1;
-    }
-    optional Event event = 3;
+    optional uint64 timeMs = 4;
 }
 
 /**
@@ -1201,35 +1311,6 @@
     optional uint64 time_ms = 3;
 }
 
-/*
- * Logs the reception of an incoming network packet causing the main system to wake up for
- * processing that packet. These events are notified by the kernel via Netlink NFLOG to Netd
- * and processed by WakeupController.cpp.
- */
-message PacketWakeupOccurred {
-    // The uid owning the socket into which the packet was delivered, or -1 if the packet was
-    // delivered nowhere.
-    optional int32 uid = 1;
-    // The interface name on which the packet was received.
-    optional string iface = 2;
-    // The ethertype value of the packet.
-    optional int32 ethertype = 3;
-    // String representation of the destination MAC address of the packet.
-    optional string destination_hardware_address = 4;
-    // String representation of the source address of the packet if this was an IP packet.
-    optional string source_ip = 5;
-    // String representation of the destination address of the packet if this was an IP packet.
-    optional string destination_ip = 6;
-    // The value of the protocol field if this was an IPv4 packet or the value of the Next Header
-    // field if this was an IPv6 packet. The range of possible values is the same for both IP
-    // families.
-    optional int32 ip_next_header = 7;
-    // The source port if this was a TCP or UDP packet.
-    optional int32 source_port = 8;
-    // The destination port if this was a TCP or UDP packet.
-    optional int32 destination_port = 9;
-}
-
 /**
  * Pulls Wifi Controller Activity Energy Info
  */
@@ -1303,37 +1384,6 @@
 }
 
 /*
- * Logs the memory stats for an app on startup.
- * Logged from:
- *     frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
- */
-message AppStartMemoryStateCaptured {
-    // The uid if available. -1 means not available.
-    optional int32 uid = 1;
-
-    // The process name.
-    optional string process_name = 2;
-
-    // The activity name.
-    optional string activity_name = 3;
-
-    // # of page-faults
-    optional int64 pgfault = 4;
-
-    // # of major page-faults
-    optional int64 pgmajfault = 5;
-
-    // RSS
-    optional int64 rss_in_bytes = 6;
-
-    // CACHE
-    optional int64 cache_in_bytes = 7;
-
-    // SWAP
-    optional int64 swap_in_bytes = 8;
-}
-
-/*
  * Logs the memory stats for a process.
  */
 message ProcessMemoryState {
@@ -1363,52 +1413,6 @@
 }
 
 /*
- * Logs the change in Low Memory Killer Daemon (LMKD) state which is used as start/stop boundaries
- * for LMK event.
- * Logged from:
- *      system/core/lmkd/lmkd.c
- */
-message LmkStateChanged {
-    enum State {
-        UNKNOWN = 0;
-        START = 1;
-        STOP = 2;
-    }
-    optional State state = 1;
-}
-
-/*
- * Logs the event when Low Memory Killer Daemon (LMKD) kills a process to reduce memory pressure.
- * Logged from:
- *      system/core/lmkd/lmkd.c
- */
-message LmkKillOccurred {
-    // The uid if available. -1 means not available.
-    optional int32 uid = 1;
-
-    // The process name.
-    optional string process_name = 2;
-
-    // oom adj score.
-    optional int32 oom_score = 3;
-
-    // # of page-faults
-    optional int64 pgfault = 4;
-
-    // # of major page-faults
-    optional int64 pgmajfault = 5;
-
-    // RSS
-    optional int64 rss_in_bytes = 6;
-
-    // CACHE
-    optional int64 cache_in_bytes = 7;
-
-    // SWAP
-    optional int64 swap_in_bytes = 8;
-}
-
-/*
  * Elapsed real time from SystemClock.
  */
 message SystemElapsedRealtime {
diff --git a/cmds/statsd/src/external/StatsPuller.cpp b/cmds/statsd/src/external/StatsPuller.cpp
index da14434..41e4705 100644
--- a/cmds/statsd/src/external/StatsPuller.cpp
+++ b/cmds/statsd/src/external/StatsPuller.cpp
@@ -59,9 +59,24 @@
     return ret;
 }
 
-void StatsPuller::ClearCache() {
+int StatsPuller::ForceClearCache() {
+    return clearCache();
+}
+
+int StatsPuller::clearCache() {
     lock_guard<std::mutex> lock(mLock);
+    int ret = mCachedData.size();
     mCachedData.clear();
+    mLastPullTimeSec = 0;
+    return ret;
+}
+
+int StatsPuller::ClearCacheIfNecessary(long timestampSec) {
+    if (timestampSec - mLastPullTimeSec > mCoolDownSec) {
+        return clearCache();
+    } else {
+        return 0;
+    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPuller.h b/cmds/statsd/src/external/StatsPuller.h
index bc7c45f..3446f9b 100644
--- a/cmds/statsd/src/external/StatsPuller.h
+++ b/cmds/statsd/src/external/StatsPuller.h
@@ -38,7 +38,11 @@
 
     bool Pull(std::vector<std::shared_ptr<LogEvent>>* data);
 
-    void ClearCache();
+    // Clear cache immediately
+    int ForceClearCache();
+
+    // Clear cache if elapsed time is more than cooldown time
+    int ClearCacheIfNecessary(long timestampSec);
 
 protected:
     // The atom tag id this puller pulls
@@ -61,6 +65,8 @@
     std::vector<std::shared_ptr<LogEvent>> mCachedData;
 
     long mLastPullTimeSec;
+
+    int clearCache();
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index 4826d96..0dee342 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -54,8 +54,12 @@
         mPullerManager.SetTimeBaseSec(timeBaseSec);
     }
 
-    void ClearPullerCache() {
-        mPullerManager.ClearPullerCache();
+    int ForceClearPullerCache() {
+        return mPullerManager.ForceClearPullerCache();
+    }
+
+    int ClearPullerCacheIfNecessary(long timestampSec) {
+        return mPullerManager.ClearPullerCacheIfNecessary(timestampSec);
     }
 
  private:
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
index 71b0abe..19b3a86 100644
--- a/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.cpp
@@ -199,10 +199,20 @@
     }
 }
 
-void StatsPullerManagerImpl::ClearPullerCache() {
+int StatsPullerManagerImpl::ForceClearPullerCache() {
+    int totalCleared = 0;
     for (auto puller : mPullers) {
-        puller.second->ClearCache();
+        totalCleared += puller.second->ForceClearCache();
     }
+    return totalCleared;
+}
+
+int StatsPullerManagerImpl::ClearPullerCacheIfNecessary(long timestampSec) {
+    int totalCleared = 0;
+    for (auto puller : mPullers) {
+        totalCleared += puller.second->ClearCacheIfNecessary(timestampSec);
+    }
+    return totalCleared;
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManagerImpl.h b/cmds/statsd/src/external/StatsPullerManagerImpl.h
index fba3ade..3535fa3 100644
--- a/cmds/statsd/src/external/StatsPullerManagerImpl.h
+++ b/cmds/statsd/src/external/StatsPullerManagerImpl.h
@@ -49,7 +49,9 @@
 
     void SetTimeBaseSec(long timeBaseSec) {mTimeBaseSec = timeBaseSec;};
 
-    void ClearPullerCache();
+    int ForceClearPullerCache();
+
+    int ClearPullerCacheIfNecessary(long timestampSec);
 
 private:
     StatsPullerManagerImpl();
diff --git a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
index 6d12e25..550a064 100644
--- a/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
+++ b/cmds/statsd/src/external/SubsystemSleepStatePuller.cpp
@@ -99,6 +99,7 @@
                         auto statePtr = make_shared<LogEvent>(android::util::SUBSYSTEM_SLEEP_STATE,
                                                               timestamp);
                         statePtr->write(state.name);
+                        statePtr->write("");
                         statePtr->write(state.totalTransitions);
                         statePtr->write(state.residencyInMsecSinceBoot);
                         statePtr->init();
@@ -110,7 +111,8 @@
                         for (auto voter : state.voters) {
                             auto voterPtr = make_shared<LogEvent>(android::util::SUBSYSTEM_SLEEP_STATE,
                                                                   timestamp);
-                            voterPtr->write((std::string)state.name + "." + (std::string)voter.name);
+                            voterPtr->write(state.name);
+                            voterPtr->write(voter.name);
                             voterPtr->write(voter.totalNumberOfTimesVotedSinceBoot);
                             voterPtr->write(voter.totalTimeInMsecVotedForSinceBoot);
                             voterPtr->init();
@@ -144,7 +146,8 @@
                                             subsystem.states[j];
                                     auto subsystemStatePtr = make_shared<LogEvent>(
                                         android::util::SUBSYSTEM_SLEEP_STATE, timestamp);
-                                    subsystemStatePtr->write((std::string)subsystem.name + "." + (std::string)state.name);
+                                    subsystemStatePtr->write(subsystem.name);
+                                    subsystemStatePtr->write(state.name);
                                     subsystemStatePtr->write(state.totalTransitions);
                                     subsystemStatePtr->write(state.residencyInMsecSinceBoot);
                                     subsystemStatePtr->init();
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 1f4bfa6..f254327 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -85,6 +85,9 @@
     // Maximum size of all files that can be written to stats directory on disk.
     static const int kMaxFileSize = 50 * 1024 * 1024;
 
+    // How long to try to clear puller cache from last time
+    static const long kPullerCacheClearIntervalSec = 1;
+
     /**
      * Report a new config has been received and report the static stats about the config.
      *
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 3eaf7a1..5a326a4 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -275,6 +275,12 @@
 
 message IncidentdDetails {
   repeated int32 section = 1;
+
+  enum Destination {
+    AUTOMATIC = 0;
+    EXPLICIT = 1;
+  }
+  optional Destination dest = 2;
 }
 
 message PerfettoDetails {
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
new file mode 100644
index 0000000..d9a8fc8
--- /dev/null
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define DEBUG true
+#include "Log.h"
+
+#include "IncidentdReporter.h"
+#include "frameworks/base/libs/incident/proto/android/os/header.pb.h"
+
+#include <android/os/IIncidentManager.h>
+#include <android/os/IncidentReportArgs.h>
+#include <binder/IBinder.h>
+#include <binder/IServiceManager.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+bool GenerateIncidentReport(const IncidentdDetails& config, const Alert& alert,
+                            const ConfigKey& configKey) {
+    if (config.section_size() == 0) {
+        VLOG("The alert %lld contains zero section in config(%d,%lld)", alert.id(),
+            configKey.GetUid(), (long long) configKey.GetId());
+        return false;
+    }
+
+    IncidentReportArgs incidentReport;
+
+    android::os::IncidentHeaderProto header;
+    header.set_alert_id(alert.id());
+    header.mutable_config_key()->set_uid(configKey.GetUid());
+    header.mutable_config_key()->set_id(configKey.GetId());
+    incidentReport.addHeader(header);
+
+    for (int i = 0; i < config.section_size(); i++) {
+        incidentReport.addSection(config.section(i));
+    }
+
+    uint8_t dest;
+    switch (config.dest()) {
+        case IncidentdDetails_Destination_AUTOMATIC:
+            dest = android::os::DEST_AUTOMATIC;
+            break;
+        case IncidentdDetails_Destination_EXPLICIT:
+            dest = android::os::DEST_EXPLICIT;
+            break;
+        default:
+            dest = android::os::DEST_AUTOMATIC;
+    }
+    incidentReport.setDest(dest);
+
+    sp<IIncidentManager> service = interface_cast<IIncidentManager>(
+            defaultServiceManager()->getService(android::String16("incident")));
+    if (service == nullptr) {
+        ALOGW("Failed to fetch incident service.");
+        return false;
+    }
+    VLOG("Calling incidentd %p", service.get());
+    binder::Status s = service->reportIncident(incidentReport);
+    VLOG("Report incident status: %s", s.toString8().string());
+    return s.isOk();
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.h b/cmds/statsd/src/subscriber/IncidentdReporter.h
new file mode 100644
index 0000000..229ed77
--- /dev/null
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "config/ConfigKey.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // Alert, IncidentdDetails
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Calls incidentd to trigger an incident report and put in dropbox for uploading.
+ */
+bool GenerateIncidentReport(const IncidentdDetails& config, const Alert& alert,
+                            const ConfigKey& configKey);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 58936fc..c173c2c 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -220,28 +220,13 @@
 Landroid/content/pm/UserInfo;->id:I
 Landroid/content/pm/UserInfo;->isPrimary()Z
 Landroid/content/res/AssetManager;->addAssetPath(Ljava/lang/String;)I
-Landroid/content/res/AssetManager;->addAssetPaths([Ljava/lang/String;)[I
-Landroid/content/res/AssetManager;->applyStyle(JIIJ[IIJJ)V
-Landroid/content/res/AssetManager;->getArraySize(I)I
 Landroid/content/res/AssetManager;->getAssignedPackageIdentifiers()Landroid/util/SparseArray;
-Landroid/content/res/AssetManager;->getCookieName(I)Ljava/lang/String;
 Landroid/content/res/AssetManager;->getResourceBagText(II)Ljava/lang/CharSequence;
-Landroid/content/res/AssetManager;->loadResourceBagValue(IILandroid/util/TypedValue;Z)I
-Landroid/content/res/AssetManager;->loadResourceValue(ISLandroid/util/TypedValue;Z)I
-Landroid/content/res/AssetManager;->loadThemeAttributeValue(JILandroid/util/TypedValue;Z)I
 Landroid/content/res/AssetManager;->mObject:J
-Landroid/content/res/AssetManager;->openNonAssetFdNative(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;
 Landroid/content/res/AssetManager;->openNonAsset(ILjava/lang/String;I)Ljava/io/InputStream;
 Landroid/content/res/AssetManager;->openNonAsset(ILjava/lang/String;)Ljava/io/InputStream;
 Landroid/content/res/AssetManager;->openNonAsset(Ljava/lang/String;I)Ljava/io/InputStream;
 Landroid/content/res/AssetManager;->openNonAsset(Ljava/lang/String;)Ljava/io/InputStream;
-Landroid/content/res/AssetManager;->openNonAssetNative(ILjava/lang/String;I)J
-Landroid/content/res/AssetManager;->openXmlAssetNative(ILjava/lang/String;)J
-Landroid/content/res/AssetManager;->resolveAttrs(JII[I[I[I[I)Z
-Landroid/content/res/AssetManager;->retrieveArray(I[I)I
-Landroid/content/res/AssetManager;->retrieveAttributes(J[I[I[I)Z
-Landroid/content/res/AssetManager;->STYLE_NUM_ENTRIES:I
-Landroid/content/res/AssetManager;->STYLE_RESOURCE_ID:I
 Landroid/content/res/DrawableCache;->getInstance(JLandroid/content/res/Resources;Landroid/content/res/Resources$Theme;)Landroid/graphics/drawable/Drawable;
 Landroid/content/res/Resources;->getCompatibilityInfo()Landroid/content/res/CompatibilityInfo;
 Landroid/content/res/ResourcesImpl;->mAccessLock:Ljava/lang/Object;
@@ -271,7 +256,6 @@
 Landroid/content/res/TypedArray;->mTheme:Landroid/content/res/Resources$Theme;
 Landroid/content/res/TypedArray;->mValue:Landroid/util/TypedValue;
 Landroid/content/res/TypedArray;->mXml:Landroid/content/res/XmlBlock$Parser;
-Landroid/content/res/XmlBlock;->close()V
 Landroid/content/res/XmlBlock;->newParser()Landroid/content/res/XmlResourceParser;
 Landroid/content/res/XmlBlock$Parser;->mParseState:J
 Landroid/content/SyncStatusInfo;->lastSuccessTime:J
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 0bc510a..aca8d48 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6362,6 +6362,8 @@
         } else {
             writer.print(prefix); writer.println("No AutofillManager");
         }
+
+        ResourcesManager.getInstance().dump(prefix, writer);
     }
 
     /**
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 5ee7ede..4626cb2 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -348,4 +348,9 @@
      * Returns is the caller has the same uid as the Recents component
      */
     public abstract boolean isCallerRecents(int callingUid);
+
+    /**
+     * Whether an UID is active or idle.
+     */
+    public abstract boolean isUidActive(int uid);
 }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8393caa..f4836b7 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -6434,7 +6434,7 @@
         public RemoteViews makeContentView(boolean increasedHeight) {
             mBuilder.mOriginalActions = mBuilder.mActions;
             mBuilder.mActions = new ArrayList<>();
-            RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
+            RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */);
             mBuilder.mActions = mBuilder.mOriginalActions;
             mBuilder.mOriginalActions = null;
             return remoteViews;
@@ -6469,11 +6469,11 @@
          */
         @Override
         public RemoteViews makeBigContentView() {
-            return makeBigContentView(false /* showRightIcon */);
+            return makeMessagingView(false /* isCollapsed */);
         }
 
         @NonNull
-        private RemoteViews makeBigContentView(boolean showRightIcon) {
+        private RemoteViews makeMessagingView(boolean isCollapsed) {
             CharSequence conversationTitle = !TextUtils.isEmpty(super.mBigContentTitle)
                     ? super.mBigContentTitle
                     : mConversationTitle;
@@ -6484,21 +6484,24 @@
                 nameReplacement = conversationTitle;
                 conversationTitle = null;
             }
+            boolean hideLargeIcon = !isCollapsed || isOneToOne;
             RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(
                     mBuilder.getMessagingLayoutResource(),
                     mBuilder.mParams.reset().hasProgress(false).title(conversationTitle).text(null)
-                            .hideLargeIcon(!showRightIcon || isOneToOne)
+                            .hideLargeIcon(hideLargeIcon)
                             .headerTextSecondary(conversationTitle)
-                            .alwaysShowReply(showRightIcon));
+                            .alwaysShowReply(isCollapsed));
             addExtras(mBuilder.mN.extras);
             // also update the end margin if there is an image
             int endMargin = R.dimen.notification_content_margin_end;
-            if (mBuilder.mN.hasLargeIcon() && showRightIcon) {
+            if (isCollapsed) {
                 endMargin = R.dimen.notification_content_plus_picture_margin_end;
             }
             contentView.setViewLayoutMarginEndDimen(R.id.notification_main_column, endMargin);
             contentView.setInt(R.id.status_bar_latest_event_content, "setLayoutColor",
                     mBuilder.resolveContrastColor());
+            contentView.setBoolean(R.id.status_bar_latest_event_content, "setIsCollapsed",
+                    isCollapsed);
             contentView.setIcon(R.id.status_bar_latest_event_content, "setLargeIcon",
                     mBuilder.mN.mLargeIcon);
             contentView.setCharSequence(R.id.status_bar_latest_event_content, "setNameReplacement",
@@ -6565,7 +6568,7 @@
          */
         @Override
         public RemoteViews makeHeadsUpContentView(boolean increasedHeight) {
-            RemoteViews remoteViews = makeBigContentView(true /* showRightIcon */);
+            RemoteViews remoteViews = makeMessagingView(true /* isCollapsed */);
             remoteViews.setInt(R.id.notification_messaging, "setMaxDisplayedLines", 1);
             return remoteViews;
         }
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index fb11272..b96e028 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -21,6 +21,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.pm.ActivityInfo;
+import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.content.res.CompatResources;
 import android.content.res.CompatibilityInfo;
@@ -34,6 +35,7 @@
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.LruCache;
 import android.util.Pair;
 import android.util.Slog;
 import android.view.Display;
@@ -41,9 +43,13 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
 
+import java.io.IOException;
+import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Objects;
 import java.util.WeakHashMap;
 import java.util.function.Predicate;
@@ -59,12 +65,7 @@
      * Predicate that returns true if a WeakReference is gc'ed.
      */
     private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
-            new Predicate<WeakReference<Resources>>() {
-                @Override
-                public boolean test(WeakReference<Resources> weakRef) {
-                    return weakRef == null || weakRef.get() == null;
-                }
-            };
+            weakRef -> weakRef == null || weakRef.get() == null;
 
     /**
      * The global compatibility settings.
@@ -89,6 +90,48 @@
      */
     private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
 
+    private static class ApkKey {
+        public final String path;
+        public final boolean sharedLib;
+        public final boolean overlay;
+
+        ApkKey(String path, boolean sharedLib, boolean overlay) {
+            this.path = path;
+            this.sharedLib = sharedLib;
+            this.overlay = overlay;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 1;
+            result = 31 * result + this.path.hashCode();
+            result = 31 * result + Boolean.hashCode(this.sharedLib);
+            result = 31 * result + Boolean.hashCode(this.overlay);
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof ApkKey)) {
+                return false;
+            }
+            ApkKey other = (ApkKey) obj;
+            return this.path.equals(other.path) && this.sharedLib == other.sharedLib
+                    && this.overlay == other.overlay;
+        }
+    }
+
+    /**
+     * The ApkAssets we are caching and intend to hold strong references to.
+     */
+    private final LruCache<ApkKey, ApkAssets> mLoadedApkAssets = new LruCache<>(15);
+
+    /**
+     * The ApkAssets that are being referenced in the wild that we can reuse, even if they aren't
+     * in our LRU cache. Bonus resources :)
+     */
+    private final ArrayMap<ApkKey, WeakReference<ApkAssets>> mCachedApkAssets = new ArrayMap<>();
+
     /**
      * Resources and base configuration override associated with an Activity.
      */
@@ -260,6 +303,41 @@
         }
     }
 
+    private @NonNull ApkAssets loadApkAssets(String path, boolean sharedLib, boolean overlay)
+            throws IOException {
+        final ApkKey newKey = new ApkKey(path, sharedLib, overlay);
+        ApkAssets apkAssets = mLoadedApkAssets.get(newKey);
+        if (apkAssets != null) {
+            return apkAssets;
+        }
+
+        // Optimistically check if this ApkAssets exists somewhere else.
+        final WeakReference<ApkAssets> apkAssetsRef = mCachedApkAssets.get(newKey);
+        if (apkAssetsRef != null) {
+            apkAssets = apkAssetsRef.get();
+            if (apkAssets != null) {
+                mLoadedApkAssets.put(newKey, apkAssets);
+                return apkAssets;
+            } else {
+                // Clean up the reference.
+                mCachedApkAssets.remove(newKey);
+            }
+        }
+
+        // We must load this from disk.
+        if (overlay) {
+            final String idmapPath = "/data/resource-cache/"
+                    + path.substring(1).replace('/', '@')
+                    + "@idmap";
+            apkAssets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/);
+        } else {
+            apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib);
+        }
+        mLoadedApkAssets.put(newKey, apkAssets);
+        mCachedApkAssets.put(newKey, new WeakReference<>(apkAssets));
+        return apkAssets;
+    }
+
     /**
      * Creates an AssetManager from the paths within the ResourcesKey.
      *
@@ -270,13 +348,15 @@
     */
     @VisibleForTesting
     protected @Nullable AssetManager createAssetManager(@NonNull final ResourcesKey key) {
-        AssetManager assets = new AssetManager();
+        final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
 
         // resDir can be null if the 'android' package is creating a new Resources object.
         // This is fine, since each AssetManager automatically loads the 'android' package
         // already.
         if (key.mResDir != null) {
-            if (assets.addAssetPath(key.mResDir) == 0) {
+            try {
+                apkAssets.add(loadApkAssets(key.mResDir, false /*sharedLib*/, false /*overlay*/));
+            } catch (IOException e) {
                 Log.e(TAG, "failed to add asset path " + key.mResDir);
                 return null;
             }
@@ -284,7 +364,10 @@
 
         if (key.mSplitResDirs != null) {
             for (final String splitResDir : key.mSplitResDirs) {
-                if (assets.addAssetPath(splitResDir) == 0) {
+                try {
+                    apkAssets.add(loadApkAssets(splitResDir, false /*sharedLib*/,
+                            false /*overlay*/));
+                } catch (IOException e) {
                     Log.e(TAG, "failed to add split asset path " + splitResDir);
                     return null;
                 }
@@ -293,7 +376,13 @@
 
         if (key.mOverlayDirs != null) {
             for (final String idmapPath : key.mOverlayDirs) {
-                assets.addOverlayPath(idmapPath);
+                try {
+                    apkAssets.add(loadApkAssets(idmapPath, false /*sharedLib*/, true /*overlay*/));
+                } catch (IOException e) {
+                    Log.w(TAG, "failed to add overlay path " + idmapPath);
+
+                    // continue.
+                }
             }
         }
 
@@ -302,16 +391,77 @@
                 if (libDir.endsWith(".apk")) {
                     // Avoid opening files we know do not have resources,
                     // like code-only .jar files.
-                    if (assets.addAssetPathAsSharedLibrary(libDir) == 0) {
+                    try {
+                        apkAssets.add(loadApkAssets(libDir, true /*sharedLib*/, false /*overlay*/));
+                    } catch (IOException e) {
                         Log.w(TAG, "Asset path '" + libDir +
                                 "' does not exist or contains no resources.");
+
+                        // continue.
                     }
                 }
             }
         }
+
+        AssetManager assets = new AssetManager();
+        assets.setApkAssets(apkAssets.toArray(new ApkAssets[apkAssets.size()]),
+                false /*invalidateCaches*/);
         return assets;
     }
 
+    private static <T> int countLiveReferences(Collection<WeakReference<T>> collection) {
+        int count = 0;
+        for (WeakReference<T> ref : collection) {
+            final T value = ref != null ? ref.get() : null;
+            if (value != null) {
+                count++;
+            }
+        }
+        return count;
+    }
+
+    /**
+     * @hide
+     */
+    public void dump(String prefix, PrintWriter printWriter) {
+        synchronized (this) {
+            IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, "  ");
+            for (int i = 0; i < prefix.length() / 2; i++) {
+                pw.increaseIndent();
+            }
+
+            pw.println("ResourcesManager:");
+            pw.increaseIndent();
+            pw.print("cached apks: total=");
+            pw.print(mLoadedApkAssets.size());
+            pw.print(" created=");
+            pw.print(mLoadedApkAssets.createCount());
+            pw.print(" evicted=");
+            pw.print(mLoadedApkAssets.evictionCount());
+            pw.print(" hit=");
+            pw.print(mLoadedApkAssets.hitCount());
+            pw.print(" miss=");
+            pw.print(mLoadedApkAssets.missCount());
+            pw.print(" max=");
+            pw.print(mLoadedApkAssets.maxSize());
+            pw.println();
+
+            pw.print("total apks: ");
+            pw.println(countLiveReferences(mCachedApkAssets.values()));
+
+            pw.print("resources: ");
+
+            int references = countLiveReferences(mResourceReferences);
+            for (ActivityResources activityResources : mActivityResourceReferences.values()) {
+                references += countLiveReferences(activityResources.activityResources);
+            }
+            pw.println(references);
+
+            pw.print("resource impls: ");
+            pw.println(countLiveReferences(mResourceImpls.values()));
+        }
+    }
+
     private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
         Configuration config;
         final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
@@ -630,28 +780,16 @@
 
                 // We will create the ResourcesImpl object outside of holding this lock.
             }
-        }
 
-        // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
-        ResourcesImpl resourcesImpl = createResourcesImpl(key);
-        if (resourcesImpl == null) {
-            return null;
-        }
-
-        synchronized (this) {
-            ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
-            if (existingResourcesImpl != null) {
-                if (DEBUG) {
-                    Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
-                            + " new impl=" + resourcesImpl);
-                }
-                resourcesImpl.getAssets().close();
-                resourcesImpl = existingResourcesImpl;
-            } else {
-                // Add this ResourcesImpl to the cache.
-                mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
+            // If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
+            ResourcesImpl resourcesImpl = createResourcesImpl(key);
+            if (resourcesImpl == null) {
+                return null;
             }
 
+            // Add this ResourcesImpl to the cache.
+            mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
+
             final Resources resources;
             if (activityToken != null) {
                 resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 8d16100..77e118c 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9404,41 +9404,6 @@
     }
 
     /**
-     * Allows/disallows printing.
-     *
-     * Called by a device owner or a profile owner.
-     * Device owner changes policy for all users. Profile owner can override it if present.
-     * Printing is enabled by default. If {@code FEATURE_PRINTING} is absent, the call is ignored.
-     *
-     * @param admin which {@link DeviceAdminReceiver} this request is associated with.
-     * @param enabled whether printing should be allowed or not.
-     * @throws SecurityException if {@code admin} is neither device, nor profile owner.
-     */
-    public void setPrintingEnabled(@NonNull ComponentName admin, boolean enabled) {
-        try {
-            mService.setPrintingEnabled(admin, enabled);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Returns whether printing is enabled for this user.
-     *
-     * Always {@code false} if {@code FEATURE_PRINTING} is absent.
-     * Otherwise, {@code true} by default.
-     *
-     * @return {@code true} iff printing is enabled.
-     */
-    public boolean isPrintingEnabled() {
-        try {
-            return mService.isPrintingEnabled();
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Called by device owner to add an override APN.
      *
      * @param admin which {@link DeviceAdminReceiver} this request is associated with
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index ef99007..5218a73 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -402,9 +402,6 @@
     CharSequence getStartUserSessionMessage(in ComponentName admin);
     CharSequence getEndUserSessionMessage(in ComponentName admin);
 
-    void setPrintingEnabled(in ComponentName admin, boolean enabled);
-    boolean isPrintingEnabled();
-
     List<String> setMeteredDataDisabled(in ComponentName admin, in List<String> packageNames);
     List<String> getMeteredDataDisabled(in ComponentName admin);
 
diff --git a/core/java/android/content/pm/InstantAppRequest.java b/core/java/android/content/pm/InstantAppRequest.java
index 38f0225..361d4e4 100644
--- a/core/java/android/content/pm/InstantAppRequest.java
+++ b/core/java/android/content/pm/InstantAppRequest.java
@@ -18,12 +18,14 @@
 
 import android.content.Intent;
 import android.os.Bundle;
+import android.text.TextUtils;
 
 /**
  * Information needed to make an instant application resolution request.
  * @hide
  */
 public final class InstantAppRequest {
+
     /** Response from the first phase of instant application resolution */
     public final AuxiliaryResolveInfo responseObj;
     /** The original intent that triggered instant application resolution */
@@ -40,6 +42,8 @@
     public final Bundle verificationBundle;
     /** Whether resolution occurs because an application is starting */
     public final boolean resolveForStart;
+    /** The instant app digest for this request */
+    public final InstantAppResolveInfo.InstantAppDigest digest;
 
     public InstantAppRequest(AuxiliaryResolveInfo responseObj, Intent origIntent,
             String resolvedType, String callingPackage, int userId, Bundle verificationBundle,
@@ -51,5 +55,11 @@
         this.userId = userId;
         this.verificationBundle = verificationBundle;
         this.resolveForStart = resolveForStart;
+        if (origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())) {
+            digest = new InstantAppResolveInfo.InstantAppDigest(
+                    origIntent.getData().getHost(), 5 /*maxDigests*/);
+        } else {
+            digest = InstantAppResolveInfo.InstantAppDigest.UNDEFINED;
+        }
     }
 }
diff --git a/core/java/android/content/pm/InstantAppResolveInfo.java b/core/java/android/content/pm/InstantAppResolveInfo.java
index 112c5da..3a95a5f 100644
--- a/core/java/android/content/pm/InstantAppResolveInfo.java
+++ b/core/java/android/content/pm/InstantAppResolveInfo.java
@@ -26,10 +26,13 @@
 
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.Random;
 
 /**
  * Describes an externally resolvable instant application. There are three states that this class
@@ -227,14 +230,25 @@
      */
     @SystemApi
     public static final class InstantAppDigest implements Parcelable {
-        private static final int DIGEST_MASK = 0xfffff000;
-
+        static final int DIGEST_MASK = 0xfffff000;
         public static final InstantAppDigest UNDEFINED =
                 new InstantAppDigest(new byte[][]{}, new int[]{});
+
+        private static Random sRandom = null;
+        static {
+            try {
+                sRandom = SecureRandom.getInstance("SHA1PRNG");
+            } catch (NoSuchAlgorithmException e) {
+                // oh well
+                sRandom = new Random();
+            }
+        }
         /** Full digest of the domain hashes */
         private final byte[][] mDigestBytes;
-        /** The first 4 bytes of the domain hashes */
+        /** The first 5 bytes of the domain hashes */
         private final int[] mDigestPrefix;
+        /** The first 5 bytes of the domain hashes interspersed with random data */
+        private int[] mDigestPrefixSecure;
 
         public InstantAppDigest(@NonNull String hostName) {
             this(hostName, -1 /*maxDigests*/);
@@ -306,6 +320,7 @@
                 }
             }
             mDigestPrefix = in.createIntArray();
+            mDigestPrefixSecure = in.createIntArray();
         }
 
         public byte[][] getDigestBytes() {
@@ -316,6 +331,26 @@
             return mDigestPrefix;
         }
 
+        /**
+         * Returns a digest prefix with additional random prefixes interspersed.
+         * @hide
+         */
+        public int[] getDigestPrefixSecure() {
+            if (this == InstantAppResolveInfo.InstantAppDigest.UNDEFINED) {
+                return getDigestPrefix();
+            } else if (mDigestPrefixSecure == null) {
+                // let's generate some random data to intersperse throughout the set of prefixes
+                final int realSize = getDigestPrefix().length;
+                final int manufacturedSize = realSize + 10 + sRandom.nextInt(10);
+                mDigestPrefixSecure = Arrays.copyOf(getDigestPrefix(), manufacturedSize);
+                for (int i = realSize; i < manufacturedSize; i++) {
+                    mDigestPrefixSecure[i] = sRandom.nextInt() & DIGEST_MASK;
+                }
+                Arrays.sort(mDigestPrefixSecure);
+            }
+            return mDigestPrefixSecure;
+        }
+
         @Override
         public int describeContents() {
             return 0;
@@ -323,6 +358,11 @@
 
         @Override
         public void writeToParcel(Parcel out, int flags) {
+            final boolean isUndefined = this == UNDEFINED;
+            out.writeBoolean(isUndefined);
+            if (isUndefined) {
+                return;
+            }
             if (mDigestBytes == null) {
                 out.writeInt(-1);
             } else {
@@ -332,6 +372,7 @@
                 }
             }
             out.writeIntArray(mDigestPrefix);
+            out.writeIntArray(mDigestPrefixSecure);
         }
 
         @SuppressWarnings("hiding")
@@ -339,6 +380,9 @@
                 new Parcelable.Creator<InstantAppDigest>() {
             @Override
             public InstantAppDigest createFromParcel(Parcel in) {
+                if (in.readBoolean() /* is undefined */) {
+                    return UNDEFINED;
+                }
                 return new InstantAppDigest(in);
             }
             @Override
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 3afc346..377942a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -54,6 +54,7 @@
 import android.content.pm.split.DefaultSplitAssetLoader;
 import android.content.pm.split.SplitAssetDependencyLoader;
 import android.content.pm.split.SplitAssetLoader;
+import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
@@ -196,10 +197,6 @@
     private static final String TAG_RESTRICT_UPDATE = "restrict-update";
     private static final String TAG_USES_SPLIT = "uses-split";
 
-    // [b/36551762] STOPSHIP remove the ability to expose components via meta-data
-    // Temporary workaround; allow meta-data to expose components to instant apps
-    private static final String META_DATA_INSTANT_APPS = "instantapps.clients.allowed";
-
     private static final String METADATA_MAX_ASPECT_RATIO = "android.max_aspect";
 
     /**
@@ -1290,7 +1287,6 @@
      */
     @Deprecated
     public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
-        final AssetManager assets = newConfiguredAssetManager();
         final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
         if (mOnlyCoreApps) {
             if (!lite.coreApp) {
@@ -1299,8 +1295,9 @@
             }
         }
 
+        final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
         try {
-            final Package pkg = parseBaseApk(apkFile, assets, flags);
+            final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
             pkg.setCodePath(apkFile.getCanonicalPath());
             pkg.setUse32bitAbi(lite.use32bitAbi);
             return pkg;
@@ -1308,28 +1305,10 @@
             throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                     "Failed to get path: " + apkFile, e);
         } finally {
-            IoUtils.closeQuietly(assets);
+            IoUtils.closeQuietly(assetLoader);
         }
     }
 
-    private static int loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
-            throws PackageParserException {
-        if ((flags & PARSE_MUST_BE_APK) != 0 && !isApkPath(apkPath)) {
-            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
-                    "Invalid package file: " + apkPath);
-        }
-
-        // The AssetManager guarantees uniqueness for asset paths, so if this asset path
-        // already exists in the AssetManager, addAssetPath will only return the cookie
-        // assigned to it.
-        int cookie = assets.addAssetPath(apkPath);
-        if (cookie == 0) {
-            throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                    "Failed adding asset path: " + apkPath);
-        }
-        return cookie;
-    }
-
     private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
             throws PackageParserException {
         final String apkPath = apkFile.getAbsolutePath();
@@ -1345,13 +1324,15 @@
 
         if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);
 
-        final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
-
-        Resources res = null;
         XmlResourceParser parser = null;
         try {
-            res = new Resources(assets, mMetrics, null);
+            final int cookie = assets.findCookieForPath(apkPath);
+            if (cookie == 0) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                        "Failed adding asset path: " + apkPath);
+            }
             parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+            final Resources res = new Resources(assets, mMetrics, null);
 
             final String[] outError = new String[1];
             final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
@@ -1386,15 +1367,18 @@
 
         if (DEBUG_JAR) Slog.d(TAG, "Scanning split APK: " + apkPath);
 
-        final int cookie = loadApkIntoAssetManager(assets, apkPath, flags);
-
         final Resources res;
         XmlResourceParser parser = null;
         try {
-            res = new Resources(assets, mMetrics, null);
-            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    Build.VERSION.RESOURCES_SDK_INT);
+            // This must always succeed, as the path has been added to the AssetManager before.
+            final int cookie = assets.findCookieForPath(apkPath);
+            if (cookie == 0) {
+                throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
+                        "Failed adding asset path: " + apkPath);
+            }
+
             parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+            res = new Resources(assets, mMetrics, null);
 
             final String[] outError = new String[1];
             pkg = parseSplitApk(pkg, res, parser, flags, splitIndex, outError);
@@ -1596,21 +1580,19 @@
             int flags) throws PackageParserException {
         final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
 
-        AssetManager assets = null;
         XmlResourceParser parser = null;
         try {
-            assets = newConfiguredAssetManager();
-            int cookie = fd != null
-                    ? assets.addAssetFd(fd, debugPathName) : assets.addAssetPath(apkPath);
-            if (cookie == 0) {
+            final ApkAssets apkAssets;
+            try {
+                apkAssets = fd != null
+                        ? ApkAssets.loadFromFd(fd, debugPathName, false, false)
+                        : ApkAssets.loadFromPath(apkPath);
+            } catch (IOException e) {
                 throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                         "Failed to parse " + apkPath);
             }
 
-            final DisplayMetrics metrics = new DisplayMetrics();
-            metrics.setToDefaults();
-
-            parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
+            parser = apkAssets.openXml(ANDROID_MANIFEST_FILENAME);
 
             final SigningDetails signingDetails;
             if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {
@@ -1637,7 +1619,7 @@
                     "Failed to parse " + apkPath, e);
         } finally {
             IoUtils.closeQuietly(parser);
-            IoUtils.closeQuietly(assets);
+            // TODO(b/72056911): Implement and call close() on ApkAssets.
         }
     }
 
@@ -4430,24 +4412,6 @@
                         outError)) == null) {
                     return null;
                 }
-                // we don't have an attribute [or it's false], but, we have meta-data
-                if (!visibleToEphemeral && a.metaData.getBoolean(META_DATA_INSTANT_APPS)) {
-                    visibleToEphemeral = true; // set in case there are more intent filters
-                    a.info.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
-                    a.info.flags &= ~ActivityInfo.FLAG_IMPLICITLY_VISIBLE_TO_INSTANT_APP;
-                    owner.visibleToInstantApps = true;
-                    // cycle through any filters already seen
-                    for (int i = a.intents.size() - 1; i >= 0; --i) {
-                        a.intents.get(i)
-                                .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
-                    }
-                    if (owner.preferredActivityFilters != null) {
-                        for (int i = owner.preferredActivityFilters.size() - 1; i >= 0; --i) {
-                            owner.preferredActivityFilters.get(i)
-                                    .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
-                        }
-                    }
-                }
             } else if (!receiver && parser.getName().equals("layout")) {
                 parseLayout(res, parser, a);
             } else {
@@ -4942,7 +4906,7 @@
         p.info.authority = cpname.intern();
 
         if (!parseProviderTags(
-                res, parser, visibleToEphemeral, owner, p, outError)) {
+                res, parser, visibleToEphemeral, p, outError)) {
             return null;
         }
 
@@ -4950,7 +4914,7 @@
     }
 
     private boolean parseProviderTags(Resources res, XmlResourceParser parser,
-            boolean visibleToEphemeral, Package owner, Provider outInfo, String[] outError)
+            boolean visibleToEphemeral, Provider outInfo, String[] outError)
                     throws XmlPullParserException, IOException {
         int outerDepth = parser.getDepth();
         int type;
@@ -4978,17 +4942,6 @@
                         outInfo.metaData, outError)) == null) {
                     return false;
                 }
-                // we don't have an attribute [or it's false], but, we have meta-data
-                if (!visibleToEphemeral && outInfo.metaData.getBoolean(META_DATA_INSTANT_APPS)) {
-                    visibleToEphemeral = true; // set in case there are more intent filters
-                    outInfo.info.flags |= ProviderInfo.FLAG_VISIBLE_TO_INSTANT_APP;
-                    owner.visibleToInstantApps = true;
-                    // cycle through any filters already seen
-                    for (int i = outInfo.intents.size() - 1; i >= 0; --i) {
-                        outInfo.intents.get(i)
-                                .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
-                    }
-                }
 
             } else if (parser.getName().equals("grant-uri-permission")) {
                 TypedArray sa = res.obtainAttributes(parser,
@@ -5277,17 +5230,6 @@
                         outError)) == null) {
                     return null;
                 }
-                // we don't have an attribute [or it's false], but, we have meta-data
-                if (!visibleToEphemeral && s.metaData.getBoolean(META_DATA_INSTANT_APPS)) {
-                    visibleToEphemeral = true; // set in case there are more intent filters
-                    s.info.flags |= ServiceInfo.FLAG_VISIBLE_TO_INSTANT_APP;
-                    owner.visibleToInstantApps = true;
-                    // cycle through any filters already seen
-                    for (int i = s.intents.size() - 1; i >= 0; --i) {
-                        s.intents.get(i)
-                                .setVisibilityToInstantApp(IntentFilter.VISIBILITY_EXPLICIT);
-                    }
-                }
             } else {
                 if (!RIGID_PARSER) {
                     Slog.w(TAG, "Unknown element under <service>: "
diff --git a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
index 99eb470..9e3a8f4 100644
--- a/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
+++ b/core/java/android/content/pm/split/DefaultSplitAssetLoader.java
@@ -15,10 +15,13 @@
  */
 package android.content.pm.split;
 
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
+import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
 
 import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.ParseFlags;
+import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.os.Build;
 
@@ -26,6 +29,8 @@
 
 import libcore.io.IoUtils;
 
+import java.io.IOException;
+
 /**
  * Loads the base and split APKs into a single AssetManager.
  * @hide
@@ -33,68 +38,66 @@
 public class DefaultSplitAssetLoader implements SplitAssetLoader {
     private final String mBaseCodePath;
     private final String[] mSplitCodePaths;
-    private final int mFlags;
-
+    private final @ParseFlags int mFlags;
     private AssetManager mCachedAssetManager;
 
-    public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, int flags) {
+    public DefaultSplitAssetLoader(PackageParser.PackageLite pkg, @ParseFlags int flags) {
         mBaseCodePath = pkg.baseCodePath;
         mSplitCodePaths = pkg.splitCodePaths;
         mFlags = flags;
     }
 
-    private static void loadApkIntoAssetManager(AssetManager assets, String apkPath, int flags)
-            throws PackageParser.PackageParserException {
-        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(apkPath)) {
-            throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
-                    "Invalid package file: " + apkPath);
+    private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
+            throws PackageParserException {
+        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                    "Invalid package file: " + path);
         }
 
-        if (assets.addAssetPath(apkPath) == 0) {
-            throw new PackageParser.PackageParserException(
-                    INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                    "Failed adding asset path: " + apkPath);
+        try {
+            return ApkAssets.loadFromPath(path);
+        } catch (IOException e) {
+            throw new PackageParserException(INSTALL_FAILED_INVALID_APK,
+                    "Failed to load APK at path " + path, e);
         }
     }
 
     @Override
-    public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+    public AssetManager getBaseAssetManager() throws PackageParserException {
         if (mCachedAssetManager != null) {
             return mCachedAssetManager;
         }
 
-        AssetManager assets = new AssetManager();
-        try {
-            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    Build.VERSION.RESOURCES_SDK_INT);
-            loadApkIntoAssetManager(assets, mBaseCodePath, mFlags);
+        ApkAssets[] apkAssets = new ApkAssets[(mSplitCodePaths != null
+                ? mSplitCodePaths.length : 0) + 1];
 
-            if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
-                for (String apkPath : mSplitCodePaths) {
-                    loadApkIntoAssetManager(assets, apkPath, mFlags);
-                }
-            }
+        // Load the base.
+        int splitIdx = 0;
+        apkAssets[splitIdx++] = loadApkAssets(mBaseCodePath, mFlags);
 
-            mCachedAssetManager = assets;
-            assets = null;
-            return mCachedAssetManager;
-        } finally {
-            if (assets != null) {
-                IoUtils.closeQuietly(assets);
+        // Load any splits.
+        if (!ArrayUtils.isEmpty(mSplitCodePaths)) {
+            for (String apkPath : mSplitCodePaths) {
+                apkAssets[splitIdx++] = loadApkAssets(apkPath, mFlags);
             }
         }
+
+        AssetManager assets = new AssetManager();
+        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                Build.VERSION.RESOURCES_SDK_INT);
+        assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
+
+        mCachedAssetManager = assets;
+        return mCachedAssetManager;
     }
 
     @Override
-    public AssetManager getSplitAssetManager(int splitIdx)
-            throws PackageParser.PackageParserException {
+    public AssetManager getSplitAssetManager(int splitIdx) throws PackageParserException {
         return getBaseAssetManager();
     }
 
     @Override
     public void close() throws Exception {
-        if (mCachedAssetManager != null) {
-            IoUtils.closeQuietly(mCachedAssetManager);
-        }
+        IoUtils.closeQuietly(mCachedAssetManager);
     }
 }
diff --git a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
index 16023f0..58eaabf 100644
--- a/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/android/content/pm/split/SplitAssetDependencyLoader.java
@@ -15,17 +15,21 @@
  */
 package android.content.pm.split;
 
-import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_BAD_MANIFEST;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NOT_APK;
 
 import android.annotation.NonNull;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.PackageParserException;
+import android.content.pm.PackageParser.ParseFlags;
+import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.os.Build;
 import android.util.SparseArray;
 
 import libcore.io.IoUtils;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
 
@@ -34,17 +38,15 @@
  * is to be used when an application opts-in to isolated split loading.
  * @hide
  */
-public class SplitAssetDependencyLoader
-        extends SplitDependencyLoader<PackageParser.PackageParserException>
+public class SplitAssetDependencyLoader extends SplitDependencyLoader<PackageParserException>
         implements SplitAssetLoader {
     private final String[] mSplitPaths;
-    private final int mFlags;
-
-    private String[][] mCachedPaths;
-    private AssetManager[] mCachedAssetManagers;
+    private final @ParseFlags int mFlags;
+    private final ApkAssets[][] mCachedSplitApks;
+    private final AssetManager[] mCachedAssetManagers;
 
     public SplitAssetDependencyLoader(PackageParser.PackageLite pkg,
-            SparseArray<int[]> dependencies, int flags) {
+            SparseArray<int[]> dependencies, @ParseFlags int flags) {
         super(dependencies);
 
         // The base is inserted into index 0, so we need to shift all the splits by 1.
@@ -53,7 +55,7 @@
         System.arraycopy(pkg.splitCodePaths, 0, mSplitPaths, 1, pkg.splitCodePaths.length);
 
         mFlags = flags;
-        mCachedPaths = new String[mSplitPaths.length][];
+        mCachedSplitApks = new ApkAssets[mSplitPaths.length][];
         mCachedAssetManagers = new AssetManager[mSplitPaths.length];
     }
 
@@ -62,58 +64,60 @@
         return mCachedAssetManagers[splitIdx] != null;
     }
 
-    private static AssetManager createAssetManagerWithPaths(String[] assetPaths, int flags)
-            throws PackageParser.PackageParserException {
-        final AssetManager assets = new AssetManager();
-        try {
-            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    Build.VERSION.RESOURCES_SDK_INT);
-
-            for (String assetPath : assetPaths) {
-                if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 &&
-                        !PackageParser.isApkPath(assetPath)) {
-                    throw new PackageParser.PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
-                            "Invalid package file: " + assetPath);
-                }
-
-                if (assets.addAssetPath(assetPath) == 0) {
-                    throw new PackageParser.PackageParserException(
-                            INSTALL_PARSE_FAILED_BAD_MANIFEST,
-                            "Failed adding asset path: " + assetPath);
-                }
-            }
-            return assets;
-        } catch (Throwable e) {
-            IoUtils.closeQuietly(assets);
-            throw e;
+    private static ApkAssets loadApkAssets(String path, @ParseFlags int flags)
+            throws PackageParserException {
+        if ((flags & PackageParser.PARSE_MUST_BE_APK) != 0 && !PackageParser.isApkPath(path)) {
+            throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
+                    "Invalid package file: " + path);
         }
+
+        try {
+            return ApkAssets.loadFromPath(path);
+        } catch (IOException e) {
+            throw new PackageParserException(PackageManager.INSTALL_FAILED_INVALID_APK,
+                    "Failed to load APK at path " + path, e);
+        }
+    }
+
+    private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
+        final AssetManager assets = new AssetManager();
+        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                Build.VERSION.RESOURCES_SDK_INT);
+        assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
+        return assets;
     }
 
     @Override
     protected void constructSplit(int splitIdx, @NonNull int[] configSplitIndices,
-            int parentSplitIdx) throws PackageParser.PackageParserException {
-        final ArrayList<String> assetPaths = new ArrayList<>();
+            int parentSplitIdx) throws PackageParserException {
+        final ArrayList<ApkAssets> assets = new ArrayList<>();
+
+        // Include parent ApkAssets.
         if (parentSplitIdx >= 0) {
-            Collections.addAll(assetPaths, mCachedPaths[parentSplitIdx]);
+            Collections.addAll(assets, mCachedSplitApks[parentSplitIdx]);
         }
 
-        assetPaths.add(mSplitPaths[splitIdx]);
+        // Include this ApkAssets.
+        assets.add(loadApkAssets(mSplitPaths[splitIdx], mFlags));
+
+        // Load and include all config splits for this feature.
         for (int configSplitIdx : configSplitIndices) {
-            assetPaths.add(mSplitPaths[configSplitIdx]);
+            assets.add(loadApkAssets(mSplitPaths[configSplitIdx], mFlags));
         }
-        mCachedPaths[splitIdx] = assetPaths.toArray(new String[assetPaths.size()]);
-        mCachedAssetManagers[splitIdx] = createAssetManagerWithPaths(mCachedPaths[splitIdx],
-                mFlags);
+
+        // Cache the results.
+        mCachedSplitApks[splitIdx] = assets.toArray(new ApkAssets[assets.size()]);
+        mCachedAssetManagers[splitIdx] = createAssetManagerWithAssets(mCachedSplitApks[splitIdx]);
     }
 
     @Override
-    public AssetManager getBaseAssetManager() throws PackageParser.PackageParserException {
+    public AssetManager getBaseAssetManager() throws PackageParserException {
         loadDependenciesForSplit(0);
         return mCachedAssetManagers[0];
     }
 
     @Override
-    public AssetManager getSplitAssetManager(int idx) throws PackageParser.PackageParserException {
+    public AssetManager getSplitAssetManager(int idx) throws PackageParserException {
         // Since we insert the base at position 0, and PackageParser keeps splits separate from
         // the base, we need to adjust the index.
         loadDependenciesForSplit(idx + 1);
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
new file mode 100644
index 0000000..fd664bc
--- /dev/null
+++ b/core/java/android/content/res/ApkAssets.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.content.res;
+
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+/**
+ * The loaded, immutable, in-memory representation of an APK.
+ *
+ * The main implementation is native C++ and there is very little API surface exposed here. The APK
+ * is mainly accessed via {@link AssetManager}.
+ *
+ * Since the ApkAssets instance is immutable, it can be reused and shared across AssetManagers,
+ * making the creation of AssetManagers very cheap.
+ * @hide
+ */
+public final class ApkAssets {
+    @GuardedBy("this") private final long mNativePtr;
+    @GuardedBy("this") private StringBlock mStringBlock;
+
+    /**
+     * Creates a new ApkAssets instance from the given path on disk.
+     *
+     * @param path The path to an APK on disk.
+     * @return a new instance of ApkAssets.
+     * @throws IOException if a disk I/O error or parsing error occurred.
+     */
+    public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
+        return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/);
+    }
+
+    /**
+     * Creates a new ApkAssets instance from the given path on disk.
+     *
+     * @param path The path to an APK on disk.
+     * @param system When true, the APK is loaded as a system APK (framework).
+     * @return a new instance of ApkAssets.
+     * @throws IOException if a disk I/O error or parsing error occurred.
+     */
+    public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system)
+            throws IOException {
+        return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/);
+    }
+
+    /**
+     * Creates a new ApkAssets instance from the given path on disk.
+     *
+     * @param path The path to an APK on disk.
+     * @param system When true, the APK is loaded as a system APK (framework).
+     * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are
+     *                           loaded as a shared library.
+     * @return a new instance of ApkAssets.
+     * @throws IOException if a disk I/O error or parsing error occurred.
+     */
+    public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system,
+            boolean forceSharedLibrary) throws IOException {
+        return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/);
+    }
+
+    /**
+     * Creates a new ApkAssets instance from the given file descriptor. Not for use by applications.
+     *
+     * Performs a dup of the underlying fd, so you must take care of still closing
+     * the FileDescriptor yourself (and can do that whenever you want).
+     *
+     * @param fd The FileDescriptor of an open, readable APK.
+     * @param friendlyName The friendly name used to identify this ApkAssets when logging.
+     * @param system When true, the APK is loaded as a system APK (framework).
+     * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are
+     *                           loaded as a shared library.
+     * @return a new instance of ApkAssets.
+     * @throws IOException if a disk I/O error or parsing error occurred.
+     */
+    public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
+            @NonNull String friendlyName, boolean system, boolean forceSharedLibrary)
+            throws IOException {
+        return new ApkAssets(fd, friendlyName, system, forceSharedLibrary);
+    }
+
+    /**
+     * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path
+     * is encoded within the IDMAP.
+     *
+     * @param idmapPath Path to the IDMAP of an overlay APK.
+     * @param system When true, the APK is loaded as a system APK (framework).
+     * @return a new instance of ApkAssets.
+     * @throws IOException if a disk I/O error or parsing error occurred.
+     */
+    public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
+            throws IOException {
+        return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/);
+    }
+
+    private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+            throws IOException {
+        Preconditions.checkNotNull(path, "path");
+        mNativePtr = nativeLoad(path, system, forceSharedLib, overlay);
+        mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+    }
+
+    private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system,
+            boolean forceSharedLib) throws IOException {
+        Preconditions.checkNotNull(fd, "fd");
+        Preconditions.checkNotNull(friendlyName, "friendlyName");
+        mNativePtr = nativeLoadFromFd(fd, friendlyName, system, forceSharedLib);
+        mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+    }
+
+    @NonNull String getAssetPath() {
+        synchronized (this) {
+            return nativeGetAssetPath(mNativePtr);
+        }
+    }
+
+    CharSequence getStringFromPool(int idx) {
+        synchronized (this) {
+            return mStringBlock.get(idx);
+        }
+    }
+
+    /**
+     * Retrieve a parser for a compiled XML file. This is associated with a single APK and
+     * <em>NOT</em> a full AssetManager. This means that shared-library references will not be
+     * dynamically assigned runtime package IDs.
+     *
+     * @param fileName The path to the file within the APK.
+     * @return An XmlResourceParser.
+     * @throws IOException if the file was not found or an error occurred retrieving it.
+     */
+    public @NonNull XmlResourceParser openXml(@NonNull String fileName) throws IOException {
+        Preconditions.checkNotNull(fileName, "fileName");
+        synchronized (this) {
+            long nativeXmlPtr = nativeOpenXml(mNativePtr, fileName);
+            try (XmlBlock block = new XmlBlock(null, nativeXmlPtr)) {
+                XmlResourceParser parser = block.newParser();
+                // If nativeOpenXml doesn't throw, it will always return a valid native pointer,
+                // which makes newParser always return non-null. But let's be paranoid.
+                if (parser == null) {
+                    throw new AssertionError("block.newParser() returned a null parser");
+                }
+                return parser;
+            }
+        }
+    }
+
+    /**
+     * Returns false if the underlying APK was changed since this ApkAssets was loaded.
+     */
+    public boolean isUpToDate() {
+        synchronized (this) {
+            return nativeIsUpToDate(mNativePtr);
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        nativeDestroy(mNativePtr);
+    }
+
+    private static native long nativeLoad(
+            @NonNull String path, boolean system, boolean forceSharedLib, boolean overlay)
+            throws IOException;
+    private static native long nativeLoadFromFd(@NonNull FileDescriptor fd,
+            @NonNull String friendlyName, boolean system, boolean forceSharedLib)
+            throws IOException;
+    private static native void nativeDestroy(long ptr);
+    private static native @NonNull String nativeGetAssetPath(long ptr);
+    private static native long nativeGetStringBlock(long ptr);
+    private static native boolean nativeIsUpToDate(long ptr);
+    private static native long nativeOpenXml(long ptr, @NonNull String fileName) throws IOException;
+}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 7866560..24116b4 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -18,9 +18,11 @@
 
 import android.annotation.AnyRes;
 import android.annotation.ArrayRes;
+import android.annotation.AttrRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
+import android.annotation.StyleRes;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration.NativeConfig;
 import android.os.ParcelFileDescriptor;
@@ -28,10 +30,20 @@
 import android.util.SparseArray;
 import android.util.TypedValue;
 
-import java.io.FileDescriptor;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.Preconditions;
+
+import libcore.io.IoUtils;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.channels.FileLock;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 
 /**
@@ -42,7 +54,19 @@
  * bytes.
  */
 public final class AssetManager implements AutoCloseable {
-    /* modes used when opening an asset */
+    private static final String TAG = "AssetManager";
+    private static final boolean DEBUG_REFS = false;
+
+    private static final String FRAMEWORK_APK_PATH = "/system/framework/framework-res.apk";
+
+    private static final Object sSync = new Object();
+
+    private static final ApkAssets[] sEmptyApkAssets = new ApkAssets[0];
+
+    // Not private for LayoutLib's BridgeAssetManager.
+    @GuardedBy("sSync") static AssetManager sSystem = null;
+
+    @GuardedBy("sSync") private static ApkAssets[] sSystemApkAssets = new ApkAssets[0];
 
     /**
      * Mode for {@link #open(String, int)}: no specific information about how
@@ -65,146 +89,298 @@
      */
     public static final int ACCESS_BUFFER = 3;
 
-    private static final String TAG = "AssetManager";
-    private static final boolean localLOGV = false || false;
-    
-    private static final boolean DEBUG_REFS = false;
-    
-    private static final Object sSync = new Object();
-    /*package*/ static AssetManager sSystem = null;
+    @GuardedBy("this") private final TypedValue mValue = new TypedValue();
+    @GuardedBy("this") private final long[] mOffsets = new long[2];
 
-    private final TypedValue mValue = new TypedValue();
-    private final long[] mOffsets = new long[2];
-    
-    // For communication with native code.
-    private long mObject;
+    // Pointer to native implementation, stuffed inside a long.
+    @GuardedBy("this") private long mObject;
 
-    private StringBlock mStringBlocks[] = null;
-    
-    private int mNumRefs = 1;
-    private boolean mOpen = true;
-    private HashMap<Long, RuntimeException> mRefStacks;
- 
+    // The loaded asset paths.
+    @GuardedBy("this") private ApkAssets[] mApkAssets;
+
+    // Debug/reference counting implementation.
+    @GuardedBy("this") private boolean mOpen = true;
+    @GuardedBy("this") private int mNumRefs = 1;
+    @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;
+
     /**
      * Create a new AssetManager containing only the basic system assets.
      * Applications will not generally use this method, instead retrieving the
      * appropriate asset manager with {@link Resources#getAssets}.    Not for
      * use by applications.
-     * {@hide}
+     * @hide
      */
     public AssetManager() {
-        synchronized (this) {
-            if (DEBUG_REFS) {
-                mNumRefs = 0;
-                incRefsLocked(this.hashCode());
-            }
-            init(false);
-            if (localLOGV) Log.v(TAG, "New asset manager: " + this);
-            ensureSystemAssets();
+        final ApkAssets[] assets;
+        synchronized (sSync) {
+            createSystemAssetsInZygoteLocked();
+            assets = sSystemApkAssets;
+        }
+
+        mObject = nativeCreate();
+        if (DEBUG_REFS) {
+            mNumRefs = 0;
+            incRefsLocked(hashCode());
+        }
+
+        // Always set the framework resources.
+        setApkAssets(assets, false /*invalidateCaches*/);
+    }
+
+    /**
+     * Private constructor that doesn't call ensureSystemAssets.
+     * Used for the creation of system assets.
+     */
+    @SuppressWarnings("unused")
+    private AssetManager(boolean sentinel) {
+        mObject = nativeCreate();
+        if (DEBUG_REFS) {
+            mNumRefs = 0;
+            incRefsLocked(hashCode());
         }
     }
 
-    private static void ensureSystemAssets() {
-        synchronized (sSync) {
-            if (sSystem == null) {
-                AssetManager system = new AssetManager(true);
-                system.makeStringBlocks(null);
-                sSystem = system;
-            }
+    /**
+     * This must be called from Zygote so that system assets are shared by all applications.
+     */
+    private static void createSystemAssetsInZygoteLocked() {
+        if (sSystem != null) {
+            return;
+        }
+
+        // Make sure that all IDMAPs are up to date.
+        nativeVerifySystemIdmaps();
+
+        try {
+            ArrayList<ApkAssets> apkAssets = new ArrayList<>();
+            apkAssets.add(ApkAssets.loadFromPath(FRAMEWORK_APK_PATH, true /*system*/));
+            loadStaticRuntimeOverlays(apkAssets);
+
+            sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]);
+            sSystem = new AssetManager(true /*sentinel*/);
+            sSystem.setApkAssets(sSystemApkAssets, false /*invalidateCaches*/);
+        } catch (IOException e) {
+            throw new IllegalStateException("Failed to create system AssetManager", e);
         }
     }
-    
-    private AssetManager(boolean isSystem) {
-        if (DEBUG_REFS) {
-            synchronized (this) {
-                mNumRefs = 0;
-                incRefsLocked(this.hashCode());
-            }
+
+    /**
+     * Loads the static runtime overlays declared in /data/resource-cache/overlays.list.
+     * Throws an exception if the file is corrupt or if loading the APKs referenced by the file
+     * fails. Returns quietly if the overlays.list file doesn't exist.
+     * @param outApkAssets The list to fill with the loaded ApkAssets.
+     */
+    private static void loadStaticRuntimeOverlays(ArrayList<ApkAssets> outApkAssets)
+            throws IOException {
+        final FileInputStream fis;
+        try {
+            fis = new FileInputStream("/data/resource-cache/overlays.list");
+        } catch (FileNotFoundException e) {
+            // We might not have any overlays, this is fine. We catch here since ApkAssets
+            // loading can also fail with the same exception, which we would want to propagate.
+            Log.i(TAG, "no overlays.list file found");
+            return;
         }
-        init(true);
-        if (localLOGV) Log.v(TAG, "New asset manager: " + this);
+
+        try {
+            // Acquire a lock so that any idmap scanning doesn't impact the current set.
+            // The order of this try-with-resources block matters. We must release the lock, and
+            // then close the file streams when exiting the block.
+            try (final BufferedReader br = new BufferedReader(new InputStreamReader(fis));
+                 final FileLock flock = fis.getChannel().lock(0, Long.MAX_VALUE, true /*shared*/)) {
+                for (String line; (line = br.readLine()) != null; ) {
+                    final String idmapPath = line.split(" ")[1];
+                    outApkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
+                }
+            }
+        } finally {
+            // When BufferedReader is closed above, FileInputStream is closed as well. But let's be
+            // paranoid.
+            IoUtils.closeQuietly(fis);
+        }
     }
 
     /**
      * Return a global shared asset manager that provides access to only
      * system assets (no application assets).
-     * {@hide}
+     * @hide
      */
     public static AssetManager getSystem() {
-        ensureSystemAssets();
-        return sSystem;
+        synchronized (sSync) {
+            createSystemAssetsInZygoteLocked();
+            return sSystem;
+        }
     }
 
     /**
      * Close this asset manager.
      */
+    @Override
     public void close() {
-        synchronized(this) {
-            //System.out.println("Release: num=" + mNumRefs
-            //                   + ", released=" + mReleased);
+        synchronized (this) {
+            if (!mOpen) {
+                return;
+            }
+
+            mOpen = false;
+            decRefsLocked(hashCode());
+        }
+    }
+
+    /**
+     * Changes the asset paths in this AssetManager. This replaces the {@link #addAssetPath(String)}
+     * family of methods.
+     *
+     * @param apkAssets The new set of paths.
+     * @param invalidateCaches Whether to invalidate any caches. This should almost always be true.
+     *                         Set this to false if you are appending new resources
+     *                         (not new configurations).
+     * @hide
+     */
+    public void setApkAssets(@NonNull ApkAssets[] apkAssets, boolean invalidateCaches) {
+        Preconditions.checkNotNull(apkAssets, "apkAssets");
+
+        // Copy the apkAssets, but prepend the system assets (framework + overlays).
+        final ApkAssets[] newApkAssets = new ApkAssets[apkAssets.length + sSystemApkAssets.length];
+        System.arraycopy(sSystemApkAssets, 0, newApkAssets, 0, sSystemApkAssets.length);
+        System.arraycopy(apkAssets, 0, newApkAssets, sSystemApkAssets.length, apkAssets.length);
+
+        synchronized (this) {
+            ensureOpenLocked();
+            mApkAssets = newApkAssets;
+            nativeSetApkAssets(mObject, mApkAssets, invalidateCaches);
+            if (invalidateCaches) {
+                // Invalidate all caches.
+                invalidateCachesLocked(-1);
+            }
+        }
+    }
+
+    /**
+     * Invalidates the caches in this AssetManager according to the bitmask `diff`.
+     *
+     * @param diff The bitmask of changes generated by {@link Configuration#diff(Configuration)}.
+     * @see ActivityInfo.Config
+     */
+    private void invalidateCachesLocked(int diff) {
+        // TODO(adamlesinski): Currently there are no caches to invalidate in Java code.
+    }
+
+    /**
+     * Returns the set of ApkAssets loaded by this AssetManager. If the AssetManager is closed, this
+     * returns a 0-length array.
+     * @hide
+     */
+    public @NonNull ApkAssets[] getApkAssets() {
+        synchronized (this) {
             if (mOpen) {
-                mOpen = false;
-                decRefsLocked(this.hashCode());
+                return mApkAssets;
             }
         }
+        return sEmptyApkAssets;
     }
 
     /**
-     * Retrieves the string value associated with a particular resource
-     * identifier for the current configuration.
-     *
-     * @param resId the resource identifier to load
-     * @return the string value, or {@code null}
+     * Returns a cookie for use with the other APIs of AssetManager.
+     * @return 0 if the path was not found, otherwise a positive integer cookie representing
+     * this path in the AssetManager.
+     * @hide
      */
-    @Nullable
-    final CharSequence getResourceText(@StringRes int resId) {
+    public int findCookieForPath(@NonNull String path) {
+        Preconditions.checkNotNull(path, "path");
         synchronized (this) {
-            final TypedValue outValue = mValue;
-            if (getResourceValue(resId, 0, outValue, true)) {
-                return outValue.coerceToString();
+            ensureValidLocked();
+            final int count = mApkAssets.length;
+            for (int i = 0; i < count; i++) {
+                if (path.equals(mApkAssets[i].getAssetPath())) {
+                    return i + 1;
+                }
             }
-            return null;
         }
+        return 0;
     }
 
     /**
-     * Retrieves the string value associated with a particular resource
-     * identifier for the current configuration.
-     *
-     * @param resId the resource identifier to load
-     * @param bagEntryId
-     * @return the string value, or {@code null}
+     * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
+     * @hide
      */
-    @Nullable
-    final CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) {
+    @Deprecated
+    public int addAssetPath(String path) {
+        return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
+    }
+
+    /**
+     * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
+     * @hide
+     */
+    @Deprecated
+    public int addAssetPathAsSharedLibrary(String path) {
+        return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/);
+    }
+
+    /**
+     * @deprecated Use {@link #setApkAssets(ApkAssets[], boolean)}
+     * @hide
+     */
+    @Deprecated
+    public int addOverlayPath(String path) {
+        return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/);
+    }
+
+    private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) {
+        Preconditions.checkNotNull(path, "path");
         synchronized (this) {
-            final TypedValue outValue = mValue;
-            final int block = loadResourceBagValue(resId, bagEntryId, outValue, true);
-            if (block < 0) {
-                return null;
+            ensureOpenLocked();
+            final int count = mApkAssets.length;
+            for (int i = 0; i < count; i++) {
+                if (mApkAssets[i].getAssetPath().equals(path)) {
+                    return i + 1;
+                }
             }
 
-            // Convert the changing configurations flags populated by native code.
-            outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
-                    outValue.changingConfigurations);
-
-            if (outValue.type == TypedValue.TYPE_STRING) {
-                return mStringBlocks[block].get(outValue.data);
+            final ApkAssets assets;
+            try {
+                if (overlay) {
+                    // TODO(b/70343104): This hardcoded path will be removed once
+                    // addAssetPathInternal is deleted.
+                    final String idmapPath = "/data/resource-cache/"
+                            + path.substring(1).replace('/', '@')
+                            + "@idmap";
+                    assets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/);
+                } else {
+                    assets = ApkAssets.loadFromPath(path, false /*system*/, appAsLib);
+                }
+            } catch (IOException e) {
+                return 0;
             }
-            return outValue.coerceToString();
+
+            final ApkAssets[] newApkAssets = Arrays.copyOf(mApkAssets, count + 1);
+            newApkAssets[count] = assets;
+            setApkAssets(newApkAssets, true);
+            return count + 1;
         }
     }
 
     /**
-     * Retrieves the string array associated with a particular resource
-     * identifier for the current configuration.
-     *
-     * @param resId the resource identifier of the string array
-     * @return the string array, or {@code null}
+     * Ensures that the native implementation has not been destroyed.
+     * The AssetManager may have been closed, but references to it still exist
+     * and therefore the native implementation is not destroyed.
      */
-    @Nullable
-    final String[] getResourceStringArray(@ArrayRes int resId) {
-        return getArrayStringResource(resId);
+    private void ensureValidLocked() {
+        if (mObject == 0) {
+            throw new RuntimeException("AssetManager has been destroyed");
+        }
+    }
+
+    /**
+     * Ensures that the AssetManager has not been explicitly closed. If this method passes,
+     * then this implies that ensureValidLocked() also passes.
+     */
+    private void ensureOpenLocked() {
+        // If mOpen is true, this implies that mObject != 0.
+        if (!mOpen) {
+            throw new RuntimeException("AssetManager has been closed");
+        }
     }
 
     /**
@@ -219,11 +395,14 @@
      * @return {@code true} if the data was loaded into {@code outValue},
      *         {@code false} otherwise
      */
-    final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
+    boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
             boolean resolveRefs) {
+        Preconditions.checkNotNull(outValue, "outValue");
         synchronized (this) {
-            final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
-            if (block < 0) {
+            ensureValidLocked();
+            final int cookie = nativeGetResourceValue(
+                    mObject, resId, (short) densityDpi, outValue, resolveRefs);
+            if (cookie <= 0) {
                 return false;
             }
 
@@ -232,38 +411,156 @@
                     outValue.changingConfigurations);
 
             if (outValue.type == TypedValue.TYPE_STRING) {
-                outValue.string = mStringBlocks[block].get(outValue.data);
+                outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
             }
             return true;
         }
     }
 
     /**
+     * Retrieves the string value associated with a particular resource
+     * identifier for the current configuration.
+     *
+     * @param resId the resource identifier to load
+     * @return the string value, or {@code null}
+     */
+    @Nullable CharSequence getResourceText(@StringRes int resId) {
+        synchronized (this) {
+            final TypedValue outValue = mValue;
+            if (getResourceValue(resId, 0, outValue, true)) {
+                return outValue.coerceToString();
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Retrieves the string value associated with a particular resource
+     * identifier for the current configuration.
+     *
+     * @param resId the resource identifier to load
+     * @param bagEntryId the index into the bag to load
+     * @return the string value, or {@code null}
+     */
+    @Nullable CharSequence getResourceBagText(@StringRes int resId, int bagEntryId) {
+        synchronized (this) {
+            ensureValidLocked();
+            final TypedValue outValue = mValue;
+            final int cookie = nativeGetResourceBagValue(mObject, resId, bagEntryId, outValue);
+            if (cookie <= 0) {
+                return null;
+            }
+
+            // Convert the changing configurations flags populated by native code.
+            outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+                    outValue.changingConfigurations);
+
+            if (outValue.type == TypedValue.TYPE_STRING) {
+                return mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+            }
+            return outValue.coerceToString();
+        }
+    }
+
+    int getResourceArraySize(@ArrayRes int resId) {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetResourceArraySize(mObject, resId);
+        }
+    }
+
+    /**
+     * Populates `outData` with array elements of `resId`. `outData` is normally
+     * used with
+     * {@link TypedArray}.
+     *
+     * Each logical element in `outData` is {@link TypedArray#STYLE_NUM_ENTRIES}
+     * long,
+     * with the indices of the data representing the type, value, asset cookie,
+     * resource ID,
+     * configuration change mask, and density of the element.
+     *
+     * @param resId The resource ID of an array resource.
+     * @param outData The array to populate with data.
+     * @return The length of the array.
+     *
+     * @see TypedArray#STYLE_TYPE
+     * @see TypedArray#STYLE_DATA
+     * @see TypedArray#STYLE_ASSET_COOKIE
+     * @see TypedArray#STYLE_RESOURCE_ID
+     * @see TypedArray#STYLE_CHANGING_CONFIGURATIONS
+     * @see TypedArray#STYLE_DENSITY
+     */
+    int getResourceArray(@ArrayRes int resId, @NonNull int[] outData) {
+        Preconditions.checkNotNull(outData, "outData");
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetResourceArray(mObject, resId, outData);
+        }
+    }
+
+    /**
+     * Retrieves the string array associated with a particular resource
+     * identifier for the current configuration.
+     *
+     * @param resId the resource identifier of the string array
+     * @return the string array, or {@code null}
+     */
+    @Nullable String[] getResourceStringArray(@ArrayRes int resId) {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetResourceStringArray(mObject, resId);
+        }
+    }
+
+    /**
      * Retrieve the text array associated with a particular resource
      * identifier.
      *
      * @param resId the resource id of the string array
      */
-    final @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
+    @Nullable CharSequence[] getResourceTextArray(@ArrayRes int resId) {
         synchronized (this) {
-            final int[] rawInfoArray = getArrayStringInfo(resId);
+            ensureValidLocked();
+            final int[] rawInfoArray = nativeGetResourceStringArrayInfo(mObject, resId);
             if (rawInfoArray == null) {
                 return null;
             }
+
             final int rawInfoArrayLen = rawInfoArray.length;
             final int infoArrayLen = rawInfoArrayLen / 2;
-            int block;
-            int index;
             final CharSequence[] retArray = new CharSequence[infoArrayLen];
             for (int i = 0, j = 0; i < rawInfoArrayLen; i = i + 2, j++) {
-                block = rawInfoArray[i];
-                index = rawInfoArray[i + 1];
-                retArray[j] = index >= 0 ? mStringBlocks[block].get(index) : null;
+                int cookie = rawInfoArray[i];
+                int index = rawInfoArray[i + 1];
+                retArray[j] = (index >= 0 && cookie > 0)
+                        ? mApkAssets[cookie - 1].getStringFromPool(index) : null;
             }
             return retArray;
         }
     }
 
+    @Nullable int[] getResourceIntArray(@ArrayRes int resId) {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetResourceIntArray(mObject, resId);
+        }
+    }
+
+    /**
+     * Get the attributes for a style resource. These are the &lt;item&gt;
+     * elements in
+     * a &lt;style&gt; resource.
+     * @param resId The resource ID of the style
+     * @return An array of attribute IDs.
+     */
+    @AttrRes int[] getStyleAttributes(@StyleRes int resId) {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetStyleAttributes(mObject, resId);
+        }
+    }
+
     /**
      * Populates {@code outValue} with the data associated with a particular
      * resource identifier for the current configuration. Resolves theme
@@ -277,73 +574,88 @@
      * @return {@code true} if the data was loaded into {@code outValue},
      *         {@code false} otherwise
      */
-    final boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue,
+    boolean getThemeValue(long theme, @AnyRes int resId, @NonNull TypedValue outValue,
             boolean resolveRefs) {
-        final int block = loadThemeAttributeValue(theme, resId, outValue, resolveRefs);
-        if (block < 0) {
-            return false;
-        }
-
-        // Convert the changing configurations flags populated by native code.
-        outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
-                outValue.changingConfigurations);
-
-        if (outValue.type == TypedValue.TYPE_STRING) {
-            final StringBlock[] blocks = ensureStringBlocks();
-            outValue.string = blocks[block].get(outValue.data);
-        }
-        return true;
-    }
-
-    /**
-     * Ensures the string blocks are loaded.
-     *
-     * @return the string blocks
-     */
-    @NonNull
-    final StringBlock[] ensureStringBlocks() {
+        Preconditions.checkNotNull(outValue, "outValue");
         synchronized (this) {
-            if (mStringBlocks == null) {
-                makeStringBlocks(sSystem.mStringBlocks);
+            ensureValidLocked();
+            final int cookie = nativeThemeGetAttributeValue(mObject, theme, resId, outValue,
+                    resolveRefs);
+            if (cookie <= 0) {
+                return false;
             }
-            return mStringBlocks;
+
+            // Convert the changing configurations flags populated by native code.
+            outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
+                    outValue.changingConfigurations);
+
+            if (outValue.type == TypedValue.TYPE_STRING) {
+                outValue.string = mApkAssets[cookie - 1].getStringFromPool(outValue.data);
+            }
+            return true;
         }
     }
 
-    /*package*/ final void makeStringBlocks(StringBlock[] seed) {
-        final int seedNum = (seed != null) ? seed.length : 0;
-        final int num = getStringBlockCount();
-        mStringBlocks = new StringBlock[num];
-        if (localLOGV) Log.v(TAG, "Making string blocks for " + this
-                + ": " + num);
-        for (int i=0; i<num; i++) {
-            if (i < seedNum) {
-                mStringBlocks[i] = seed[i];
-            } else {
-                mStringBlocks[i] = new StringBlock(getNativeStringBlock(i), true);
-            }
-        }
-    }
-
-    /*package*/ final CharSequence getPooledStringForCookie(int cookie, int id) {
+    void dumpTheme(long theme, int priority, String tag, String prefix) {
         synchronized (this) {
-            // Cookies map to string blocks starting at 1.
-            return mStringBlocks[cookie - 1].get(id);
+            ensureValidLocked();
+            nativeThemeDump(mObject, theme, priority, tag, prefix);
         }
     }
 
+    @Nullable String getResourceName(@AnyRes int resId) {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetResourceName(mObject, resId);
+        }
+    }
+
+    @Nullable String getResourcePackageName(@AnyRes int resId) {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetResourcePackageName(mObject, resId);
+        }
+    }
+
+    @Nullable String getResourceTypeName(@AnyRes int resId) {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetResourceTypeName(mObject, resId);
+        }
+    }
+
+    @Nullable String getResourceEntryName(@AnyRes int resId) {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetResourceEntryName(mObject, resId);
+        }
+    }
+
+    @AnyRes int getResourceIdentifier(@NonNull String name, @Nullable String defType,
+            @Nullable String defPackage) {
+        synchronized (this) {
+            ensureValidLocked();
+            // name is checked in JNI.
+            return nativeGetResourceIdentifier(mObject, name, defType, defPackage);
+        }
+    }
+
+    CharSequence getPooledStringForCookie(int cookie, int id) {
+        // Cookies map to ApkAssets starting at 1.
+        return getApkAssets()[cookie - 1].getStringFromPool(id);
+    }
+
     /**
      * Open an asset using ACCESS_STREAMING mode.  This provides access to
      * files that have been bundled with an application as assets -- that is,
      * files placed in to the "assets" directory.
      * 
-     * @param fileName The name of the asset to open.  This name can be
-     *                 hierarchical.
+     * @param fileName The name of the asset to open.  This name can be hierarchical.
      * 
      * @see #open(String, int)
      * @see #list
      */
-    public final InputStream open(String fileName) throws IOException {
+    public @NonNull InputStream open(@NonNull String fileName) throws IOException {
         return open(fileName, ACCESS_STREAMING);
     }
 
@@ -353,8 +665,7 @@
      * with an application as assets -- that is, files placed in to the
      * "assets" directory.
      * 
-     * @param fileName The name of the asset to open.  This name can be
-     *                 hierarchical.
+     * @param fileName The name of the asset to open.  This name can be hierarchical.
      * @param accessMode Desired access mode for retrieving the data.
      * 
      * @see #ACCESS_UNKNOWN
@@ -364,34 +675,40 @@
      * @see #open(String)
      * @see #list
      */
-    public final InputStream open(String fileName, int accessMode)
-        throws IOException {
+    public @NonNull InputStream open(@NonNull String fileName, int accessMode) throws IOException {
+        Preconditions.checkNotNull(fileName, "fileName");
         synchronized (this) {
-            if (!mOpen) {
-                throw new RuntimeException("Assetmanager has been closed");
+            ensureOpenLocked();
+            final long asset = nativeOpenAsset(mObject, fileName, accessMode);
+            if (asset == 0) {
+                throw new FileNotFoundException("Asset file: " + fileName);
             }
-            long asset = openAsset(fileName, accessMode);
-            if (asset != 0) {
-                AssetInputStream res = new AssetInputStream(asset);
-                incRefsLocked(res.hashCode());
-                return res;
-            }
+            final AssetInputStream assetInputStream = new AssetInputStream(asset);
+            incRefsLocked(assetInputStream.hashCode());
+            return assetInputStream;
         }
-        throw new FileNotFoundException("Asset file: " + fileName);
     }
 
-    public final AssetFileDescriptor openFd(String fileName)
-            throws IOException {
+    /**
+     * Open an uncompressed asset by mmapping it and returning an {@link AssetFileDescriptor}.
+     * This provides access to files that have been bundled with an application as assets -- that
+     * is, files placed in to the "assets" directory.
+     *
+     * The asset must be uncompressed, or an exception will be thrown.
+     *
+     * @param fileName The name of the asset to open.  This name can be hierarchical.
+     * @return An open AssetFileDescriptor.
+     */
+    public @NonNull AssetFileDescriptor openFd(@NonNull String fileName) throws IOException {
+        Preconditions.checkNotNull(fileName, "fileName");
         synchronized (this) {
-            if (!mOpen) {
-                throw new RuntimeException("Assetmanager has been closed");
+            ensureOpenLocked();
+            final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets);
+            if (pfd == null) {
+                throw new FileNotFoundException("Asset file: " + fileName);
             }
-            ParcelFileDescriptor pfd = openAssetFd(fileName, mOffsets);
-            if (pfd != null) {
-                return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
-            }
+            return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
         }
-        throw new FileNotFoundException("Asset file: " + fileName);
     }
 
     /**
@@ -406,90 +723,121 @@
      * 
      * @see #open
      */
-    public native final String[] list(String path)
-        throws IOException;
+    public @Nullable String[] list(@NonNull String path) throws IOException {
+        Preconditions.checkNotNull(path, "path");
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeList(mObject, path);
+        }
+    }
 
     /**
-     * {@hide}
      * Open a non-asset file as an asset using ACCESS_STREAMING mode.  This
      * provides direct access to all of the files included in an application
      * package (not only its assets).  Applications should not normally use
      * this.
-     * 
+     *
+     * @param fileName Name of the asset to retrieve.
+     *
      * @see #open(String)
+     * @hide
      */
-    public final InputStream openNonAsset(String fileName) throws IOException {
+    public @NonNull InputStream openNonAsset(@NonNull String fileName) throws IOException {
         return openNonAsset(0, fileName, ACCESS_STREAMING);
     }
 
     /**
-     * {@hide}
      * Open a non-asset file as an asset using a specific access mode.  This
      * provides direct access to all of the files included in an application
      * package (not only its assets).  Applications should not normally use
      * this.
-     * 
+     *
+     * @param fileName Name of the asset to retrieve.
+     * @param accessMode Desired access mode for retrieving the data.
+     *
+     * @see #ACCESS_UNKNOWN
+     * @see #ACCESS_STREAMING
+     * @see #ACCESS_RANDOM
+     * @see #ACCESS_BUFFER
      * @see #open(String, int)
+     * @hide
      */
-    public final InputStream openNonAsset(String fileName, int accessMode)
-        throws IOException {
+    public @NonNull InputStream openNonAsset(@NonNull String fileName, int accessMode)
+            throws IOException {
         return openNonAsset(0, fileName, accessMode);
     }
 
     /**
-     * {@hide}
      * Open a non-asset in a specified package.  Not for use by applications.
-     * 
+     *
      * @param cookie Identifier of the package to be opened.
      * @param fileName Name of the asset to retrieve.
+     * @hide
      */
-    public final InputStream openNonAsset(int cookie, String fileName)
-        throws IOException {
+    public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName)
+            throws IOException {
         return openNonAsset(cookie, fileName, ACCESS_STREAMING);
     }
 
     /**
-     * {@hide}
      * Open a non-asset in a specified package.  Not for use by applications.
-     * 
+     *
      * @param cookie Identifier of the package to be opened.
      * @param fileName Name of the asset to retrieve.
      * @param accessMode Desired access mode for retrieving the data.
+     * @hide
      */
-    public final InputStream openNonAsset(int cookie, String fileName, int accessMode)
-        throws IOException {
+    public @NonNull InputStream openNonAsset(int cookie, @NonNull String fileName, int accessMode)
+            throws IOException {
+        Preconditions.checkNotNull(fileName, "fileName");
         synchronized (this) {
-            if (!mOpen) {
-                throw new RuntimeException("Assetmanager has been closed");
+            ensureOpenLocked();
+            final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode);
+            if (asset == 0) {
+                throw new FileNotFoundException("Asset absolute file: " + fileName);
             }
-            long asset = openNonAssetNative(cookie, fileName, accessMode);
-            if (asset != 0) {
-                AssetInputStream res = new AssetInputStream(asset);
-                incRefsLocked(res.hashCode());
-                return res;
-            }
+            final AssetInputStream assetInputStream = new AssetInputStream(asset);
+            incRefsLocked(assetInputStream.hashCode());
+            return assetInputStream;
         }
-        throw new FileNotFoundException("Asset absolute file: " + fileName);
     }
 
-    public final AssetFileDescriptor openNonAssetFd(String fileName)
+    /**
+     * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}.
+     * This provides direct access to all of the files included in an application
+     * package (not only its assets).  Applications should not normally use this.
+     *
+     * The asset must not be compressed, or an exception will be thrown.
+     *
+     * @param fileName Name of the asset to retrieve.
+     */
+    public @NonNull AssetFileDescriptor openNonAssetFd(@NonNull String fileName)
             throws IOException {
         return openNonAssetFd(0, fileName);
     }
-    
-    public final AssetFileDescriptor openNonAssetFd(int cookie,
-            String fileName) throws IOException {
+
+    /**
+     * Open a non-asset as an asset by mmapping it and returning an {@link AssetFileDescriptor}.
+     * This provides direct access to all of the files included in an application
+     * package (not only its assets).  Applications should not normally use this.
+     *
+     * The asset must not be compressed, or an exception will be thrown.
+     *
+     * @param cookie Identifier of the package to be opened.
+     * @param fileName Name of the asset to retrieve.
+     */
+    public @NonNull AssetFileDescriptor openNonAssetFd(int cookie, @NonNull String fileName)
+            throws IOException {
+        Preconditions.checkNotNull(fileName, "fileName");
         synchronized (this) {
-            if (!mOpen) {
-                throw new RuntimeException("Assetmanager has been closed");
+            ensureOpenLocked();
+            final ParcelFileDescriptor pfd =
+                    nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets);
+            if (pfd == null) {
+                throw new FileNotFoundException("Asset absolute file: " + fileName);
             }
-            ParcelFileDescriptor pfd = openNonAssetFdNative(cookie,
-                    fileName, mOffsets);
-            if (pfd != null) {
-                return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
-            }
+            return new AssetFileDescriptor(pfd, mOffsets[0], mOffsets[1]);
         }
-        throw new FileNotFoundException("Asset absolute file: " + fileName);
     }
     
     /**
@@ -497,7 +845,7 @@
      * 
      * @param fileName The name of the file to retrieve.
      */
-    public final XmlResourceParser openXmlResourceParser(String fileName)
+    public @NonNull XmlResourceParser openXmlResourceParser(@NonNull String fileName)
             throws IOException {
         return openXmlResourceParser(0, fileName);
     }
@@ -508,270 +856,265 @@
      * @param cookie Identifier of the package to be opened.
      * @param fileName The name of the file to retrieve.
      */
-    public final XmlResourceParser openXmlResourceParser(int cookie,
-            String fileName) throws IOException {
-        XmlBlock block = openXmlBlockAsset(cookie, fileName);
-        XmlResourceParser rp = block.newParser();
-        block.close();
-        return rp;
+    public @NonNull XmlResourceParser openXmlResourceParser(int cookie, @NonNull String fileName)
+            throws IOException {
+        try (XmlBlock block = openXmlBlockAsset(cookie, fileName)) {
+            XmlResourceParser parser = block.newParser();
+            // If openXmlBlockAsset doesn't throw, it will always return an XmlBlock object with
+            // a valid native pointer, which makes newParser always return non-null. But let's
+            // be paranoid.
+            if (parser == null) {
+                throw new AssertionError("block.newParser() returned a null parser");
+            }
+            return parser;
+        }
     }
 
     /**
-     * {@hide}
-     * Retrieve a non-asset as a compiled XML file.  Not for use by
-     * applications.
+     * Retrieve a non-asset as a compiled XML file.  Not for use by applications.
      * 
      * @param fileName The name of the file to retrieve.
+     * @hide
      */
-    /*package*/ final XmlBlock openXmlBlockAsset(String fileName)
-            throws IOException {
+    @NonNull XmlBlock openXmlBlockAsset(@NonNull String fileName) throws IOException {
         return openXmlBlockAsset(0, fileName);
     }
 
     /**
-     * {@hide}
      * Retrieve a non-asset as a compiled XML file.  Not for use by
      * applications.
      * 
      * @param cookie Identifier of the package to be opened.
      * @param fileName Name of the asset to retrieve.
+     * @hide
      */
-    /*package*/ final XmlBlock openXmlBlockAsset(int cookie, String fileName)
-        throws IOException {
+    @NonNull XmlBlock openXmlBlockAsset(int cookie, @NonNull String fileName) throws IOException {
+        Preconditions.checkNotNull(fileName, "fileName");
         synchronized (this) {
-            if (!mOpen) {
-                throw new RuntimeException("Assetmanager has been closed");
+            ensureOpenLocked();
+            final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
+            if (xmlBlock == 0) {
+                throw new FileNotFoundException("Asset XML file: " + fileName);
             }
-            long xmlBlock = openXmlAssetNative(cookie, fileName);
-            if (xmlBlock != 0) {
-                XmlBlock res = new XmlBlock(this, xmlBlock);
-                incRefsLocked(res.hashCode());
-                return res;
-            }
+            final XmlBlock block = new XmlBlock(this, xmlBlock);
+            incRefsLocked(block.hashCode());
+            return block;
         }
-        throw new FileNotFoundException("Asset XML file: " + fileName);
     }
 
-    /*package*/ void xmlBlockGone(int id) {
+    void xmlBlockGone(int id) {
         synchronized (this) {
             decRefsLocked(id);
         }
     }
 
-    /*package*/ final long createTheme() {
+    void applyStyle(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
+            @Nullable XmlBlock.Parser parser, @NonNull int[] inAttrs, long outValuesAddress,
+            long outIndicesAddress) {
+        Preconditions.checkNotNull(inAttrs, "inAttrs");
         synchronized (this) {
-            if (!mOpen) {
-                throw new RuntimeException("Assetmanager has been closed");
-            }
-            long res = newTheme();
-            incRefsLocked(res);
-            return res;
+            // Need to synchronize on AssetManager because we will be accessing
+            // the native implementation of AssetManager.
+            ensureValidLocked();
+            nativeApplyStyle(mObject, themePtr, defStyleAttr, defStyleRes,
+                    parser != null ? parser.mParseState : 0, inAttrs, outValuesAddress,
+                    outIndicesAddress);
         }
     }
 
-    /*package*/ final void releaseTheme(long theme) {
+    boolean resolveAttrs(long themePtr, @AttrRes int defStyleAttr, @StyleRes int defStyleRes,
+            @Nullable int[] inValues, @NonNull int[] inAttrs, @NonNull int[] outValues,
+            @NonNull int[] outIndices) {
+        Preconditions.checkNotNull(inAttrs, "inAttrs");
+        Preconditions.checkNotNull(outValues, "outValues");
+        Preconditions.checkNotNull(outIndices, "outIndices");
         synchronized (this) {
-            deleteTheme(theme);
-            decRefsLocked(theme);
+            // Need to synchronize on AssetManager because we will be accessing
+            // the native implementation of AssetManager.
+            ensureValidLocked();
+            return nativeResolveAttrs(mObject,
+                    themePtr, defStyleAttr, defStyleRes, inValues, inAttrs, outValues, outIndices);
         }
     }
 
+    boolean retrieveAttributes(@NonNull XmlBlock.Parser parser, @NonNull int[] inAttrs,
+            @NonNull int[] outValues, @NonNull int[] outIndices) {
+        Preconditions.checkNotNull(parser, "parser");
+        Preconditions.checkNotNull(inAttrs, "inAttrs");
+        Preconditions.checkNotNull(outValues, "outValues");
+        Preconditions.checkNotNull(outIndices, "outIndices");
+        synchronized (this) {
+            // Need to synchronize on AssetManager because we will be accessing
+            // the native implementation of AssetManager.
+            ensureValidLocked();
+            return nativeRetrieveAttributes(
+                    mObject, parser.mParseState, inAttrs, outValues, outIndices);
+        }
+    }
+
+    long createTheme() {
+        synchronized (this) {
+            ensureValidLocked();
+            long themePtr = nativeThemeCreate(mObject);
+            incRefsLocked(themePtr);
+            return themePtr;
+        }
+    }
+
+    void releaseTheme(long themePtr) {
+        synchronized (this) {
+            nativeThemeDestroy(themePtr);
+            decRefsLocked(themePtr);
+        }
+    }
+
+    void applyStyleToTheme(long themePtr, @StyleRes int resId, boolean force) {
+        synchronized (this) {
+            // Need to synchronize on AssetManager because we will be accessing
+            // the native implementation of AssetManager.
+            ensureValidLocked();
+            nativeThemeApplyStyle(mObject, themePtr, resId, force);
+        }
+    }
+
+    @Override
     protected void finalize() throws Throwable {
-        try {
-            if (DEBUG_REFS && mNumRefs != 0) {
-                Log.w(TAG, "AssetManager " + this
-                        + " finalized with non-zero refs: " + mNumRefs);
-                if (mRefStacks != null) {
-                    for (RuntimeException e : mRefStacks.values()) {
-                        Log.w(TAG, "Reference from here", e);
-                    }
+        if (DEBUG_REFS && mNumRefs != 0) {
+            Log.w(TAG, "AssetManager " + this + " finalized with non-zero refs: " + mNumRefs);
+            if (mRefStacks != null) {
+                for (RuntimeException e : mRefStacks.values()) {
+                    Log.w(TAG, "Reference from here", e);
                 }
             }
-            destroy();
-        } finally {
-            super.finalize();
+        }
+
+        if (mObject != 0) {
+            nativeDestroy(mObject);
         }
     }
-    
+
+    /* No Locking is needed for AssetInputStream because an AssetInputStream is not-thread
+    safe and it does not rely on AssetManager once it has been created. It completely owns the
+    underlying Asset. */
     public final class AssetInputStream extends InputStream {
+        private long mAssetNativePtr;
+        private long mLength;
+        private long mMarkPos;
+
         /**
          * @hide
          */
         public final int getAssetInt() {
             throw new UnsupportedOperationException();
         }
+
         /**
          * @hide
          */
         public final long getNativeAsset() {
-            return mAsset;
+            return mAssetNativePtr;
         }
-        private AssetInputStream(long asset)
-        {
-            mAsset = asset;
-            mLength = getAssetLength(asset);
+
+        private AssetInputStream(long assetNativePtr) {
+            mAssetNativePtr = assetNativePtr;
+            mLength = nativeAssetGetLength(assetNativePtr);
         }
+
+        @Override
         public final int read() throws IOException {
-            return readAssetChar(mAsset);
+            ensureOpen();
+            return nativeAssetReadChar(mAssetNativePtr);
         }
-        public final boolean markSupported() {
-            return true;
+
+        @Override
+        public final int read(@NonNull byte[] b) throws IOException {
+            ensureOpen();
+            Preconditions.checkNotNull(b, "b");
+            return nativeAssetRead(mAssetNativePtr, b, 0, b.length);
         }
-        public final int available() throws IOException {
-            long len = getAssetRemainingLength(mAsset);
-            return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)len;
+
+        @Override
+        public final int read(@NonNull byte[] b, int off, int len) throws IOException {
+            ensureOpen();
+            Preconditions.checkNotNull(b, "b");
+            return nativeAssetRead(mAssetNativePtr, b, off, len);
         }
-        public final void close() throws IOException {
-            synchronized (AssetManager.this) {
-                if (mAsset != 0) {
-                    destroyAsset(mAsset);
-                    mAsset = 0;
-                    decRefsLocked(hashCode());
-                }
-            }
-        }
-        public final void mark(int readlimit) {
-            mMarkPos = seekAsset(mAsset, 0, 0);
-        }
-        public final void reset() throws IOException {
-            seekAsset(mAsset, mMarkPos, -1);
-        }
-        public final int read(byte[] b) throws IOException {
-            return readAsset(mAsset, b, 0, b.length);
-        }
-        public final int read(byte[] b, int off, int len) throws IOException {
-            return readAsset(mAsset, b, off, len);
-        }
+
+        @Override
         public final long skip(long n) throws IOException {
-            long pos = seekAsset(mAsset, 0, 0);
-            if ((pos+n) > mLength) {
-                n = mLength-pos;
+            ensureOpen();
+            long pos = nativeAssetSeek(mAssetNativePtr, 0, 0);
+            if ((pos + n) > mLength) {
+                n = mLength - pos;
             }
             if (n > 0) {
-                seekAsset(mAsset, n, 0);
+                nativeAssetSeek(mAssetNativePtr, n, 0);
             }
             return n;
         }
 
-        protected void finalize() throws Throwable
-        {
+        @Override
+        public final int available() throws IOException {
+            ensureOpen();
+            final long len = nativeAssetGetRemainingLength(mAssetNativePtr);
+            return len > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) len;
+        }
+
+        @Override
+        public final boolean markSupported() {
+            return true;
+        }
+
+        @Override
+        public final void mark(int readlimit) {
+            ensureOpen();
+            mMarkPos = nativeAssetSeek(mAssetNativePtr, 0, 0);
+        }
+
+        @Override
+        public final void reset() throws IOException {
+            ensureOpen();
+            nativeAssetSeek(mAssetNativePtr, mMarkPos, -1);
+        }
+
+        @Override
+        public final void close() throws IOException {
+            if (mAssetNativePtr != 0) {
+                nativeAssetDestroy(mAssetNativePtr);
+                mAssetNativePtr = 0;
+
+                synchronized (AssetManager.this) {
+                    decRefsLocked(hashCode());
+                }
+            }
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
             close();
         }
 
-        private long mAsset;
-        private long mLength;
-        private long mMarkPos;
-    }
-
-    /**
-     * Add an additional set of assets to the asset manager.  This can be
-     * either a directory or ZIP file.  Not for use by applications.  Returns
-     * the cookie of the added asset, or 0 on failure.
-     * {@hide}
-     */
-    public final int addAssetPath(String path) {
-        return  addAssetPathInternal(path, false);
-    }
-
-    /**
-     * Add an application assets to the asset manager and loading it as shared library.
-     * This can be either a directory or ZIP file.  Not for use by applications.  Returns
-     * the cookie of the added asset, or 0 on failure.
-     * {@hide}
-     */
-    public final int addAssetPathAsSharedLibrary(String path) {
-        return addAssetPathInternal(path, true);
-    }
-
-    private final int addAssetPathInternal(String path, boolean appAsLib) {
-        synchronized (this) {
-            int res = addAssetPathNative(path, appAsLib);
-            makeStringBlocks(mStringBlocks);
-            return res;
+        private void ensureOpen() {
+            if (mAssetNativePtr == 0) {
+                throw new IllegalStateException("AssetInputStream is closed");
+            }
         }
     }
 
-    private native final int addAssetPathNative(String path, boolean appAsLib);
-
-    /**
-     * Add an additional set of assets to the asset manager from an already open
-     * FileDescriptor.  Not for use by applications.
-     * This does not give full AssetManager functionality for these assets,
-     * since the origin of the file is not known for purposes of sharing,
-     * overlay resolution, and other features.  However it does allow you
-     * to do simple access to the contents of the given fd as an apk file.
-     * Performs a dup of the underlying fd, so you must take care of still closing
-     * the FileDescriptor yourself (and can do that whenever you want).
-     * Returns the cookie of the added asset, or 0 on failure.
-     * {@hide}
-     */
-    public int addAssetFd(FileDescriptor fd, String debugPathName) {
-        return addAssetFdInternal(fd, debugPathName, false);
-    }
-
-    private int addAssetFdInternal(FileDescriptor fd, String debugPathName,
-            boolean appAsLib) {
-        synchronized (this) {
-            int res = addAssetFdNative(fd, debugPathName, appAsLib);
-            makeStringBlocks(mStringBlocks);
-            return res;
-        }
-    }
-
-    private native int addAssetFdNative(FileDescriptor fd, String debugPathName,
-            boolean appAsLib);
-
-    /**
-     * Add a set of assets to overlay an already added set of assets.
-     *
-     * This is only intended for application resources. System wide resources
-     * are handled before any Java code is executed.
-     *
-     * {@hide}
-     */
-
-    public final int addOverlayPath(String idmapPath) {
-        synchronized (this) {
-            int res = addOverlayPathNative(idmapPath);
-            makeStringBlocks(mStringBlocks);
-            return res;
-        }
-    }
-
-    /**
-     * See addOverlayPath.
-     *
-     * {@hide}
-     */
-    public native final int addOverlayPathNative(String idmapPath);
-
-    /**
-     * Add multiple sets of assets to the asset manager at once.  See
-     * {@link #addAssetPath(String)} for more information.  Returns array of
-     * cookies for each added asset with 0 indicating failure, or null if
-     * the input array of paths is null.
-     * {@hide}
-     */
-    public final int[] addAssetPaths(String[] paths) {
-        if (paths == null) {
-            return null;
-        }
-
-        int[] cookies = new int[paths.length];
-        for (int i = 0; i < paths.length; i++) {
-            cookies[i] = addAssetPath(paths[i]);
-        }
-
-        return cookies;
-    }
-
     /**
      * Determine whether the state in this asset manager is up-to-date with
      * the files on the filesystem.  If false is returned, you need to
      * instantiate a new AssetManager class to see the new data.
-     * {@hide}
+     * @hide
      */
-    public native final boolean isUpToDate();
+    public boolean isUpToDate() {
+        for (ApkAssets apkAssets : getApkAssets()) {
+            if (!apkAssets.isUpToDate()) {
+                return false;
+            }
+        }
+        return true;
+    }
 
     /**
      * Get the locales that this asset manager contains data for.
@@ -784,7 +1127,12 @@
      * are of the form {@code ll_CC} where {@code ll} is a two letter language code,
      * and {@code CC} is a two letter country code.
      */
-    public native final String[] getLocales();
+    public String[] getLocales() {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetLocales(mObject, false /*excludeSystem*/);
+        }
+    }
 
     /**
      * Same as getLocales(), except that locales that are only provided by the system (i.e. those
@@ -794,131 +1142,57 @@
      * assets support Cherokee and French, getLocales() would return
      * [Cherokee, English, French, German], while getNonSystemLocales() would return
      * [Cherokee, French].
-     * {@hide}
+     * @hide
      */
-    public native final String[] getNonSystemLocales();
-
-    /** {@hide} */
-    public native final Configuration[] getSizeConfigurations();
+    public String[] getNonSystemLocales() {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetLocales(mObject, true /*excludeSystem*/);
+        }
+    }
 
     /**
-     * Change the configuation used when retrieving resources.  Not for use by
+     * @hide
+     */
+    Configuration[] getSizeConfigurations() {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetSizeConfigurations(mObject);
+        }
+    }
+
+    /**
+     * Change the configuration used when retrieving resources.  Not for use by
      * applications.
-     * {@hide}
+     * @hide
      */
-    public native final void setConfiguration(int mcc, int mnc, String locale,
-            int orientation, int touchscreen, int density, int keyboard,
-            int keyboardHidden, int navigation, int screenWidth, int screenHeight,
-            int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp,
-            int screenLayout, int uiMode, int colorMode, int majorVersion);
+    public void setConfiguration(int mcc, int mnc, @Nullable String locale, int orientation,
+            int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
+            int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
+            int screenHeightDp, int screenLayout, int uiMode, int colorMode, int majorVersion) {
+        synchronized (this) {
+            ensureValidLocked();
+            nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density,
+                    keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
+                    smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
+                    colorMode, majorVersion);
+        }
+    }
 
     /**
-     * Retrieve the resource identifier for the given resource name.
+     * @hide
      */
-    /*package*/ native final int getResourceIdentifier(String name,
-                                                       String defType,
-                                                       String defPackage);
+    public SparseArray<String> getAssignedPackageIdentifiers() {
+        synchronized (this) {
+            ensureValidLocked();
+            return nativeGetAssignedPackageIdentifiers(mObject);
+        }
+    }
 
-    /*package*/ native final String getResourceName(int resid);
-    /*package*/ native final String getResourcePackageName(int resid);
-    /*package*/ native final String getResourceTypeName(int resid);
-    /*package*/ native final String getResourceEntryName(int resid);
-    
-    private native final long openAsset(String fileName, int accessMode);
-    private final native ParcelFileDescriptor openAssetFd(String fileName,
-            long[] outOffsets) throws IOException;
-    private native final long openNonAssetNative(int cookie, String fileName,
-            int accessMode);
-    private native ParcelFileDescriptor openNonAssetFdNative(int cookie,
-            String fileName, long[] outOffsets) throws IOException;
-    private native final void destroyAsset(long asset);
-    private native final int readAssetChar(long asset);
-    private native final int readAsset(long asset, byte[] b, int off, int len);
-    private native final long seekAsset(long asset, long offset, int whence);
-    private native final long getAssetLength(long asset);
-    private native final long getAssetRemainingLength(long asset);
-
-    /** Returns true if the resource was found, filling in mRetStringBlock and
-     *  mRetData. */
-    private native final int loadResourceValue(int ident, short density, TypedValue outValue,
-            boolean resolve);
-    /** Returns true if the resource was found, filling in mRetStringBlock and
-     *  mRetData. */
-    private native final int loadResourceBagValue(int ident, int bagEntryId, TypedValue outValue,
-                                               boolean resolve);
-    /*package*/ static final int STYLE_NUM_ENTRIES = 6;
-    /*package*/ static final int STYLE_TYPE = 0;
-    /*package*/ static final int STYLE_DATA = 1;
-    /*package*/ static final int STYLE_ASSET_COOKIE = 2;
-    /*package*/ static final int STYLE_RESOURCE_ID = 3;
-
-    /* Offset within typed data array for native changingConfigurations. */
-    static final int STYLE_CHANGING_CONFIGURATIONS = 4;
-
-    /*package*/ static final int STYLE_DENSITY = 5;
-    /*package*/ native static final void applyStyle(long theme,
-            int defStyleAttr, int defStyleRes, long xmlParser,
-            int[] inAttrs, int length, long outValuesAddress, long outIndicesAddress);
-    /*package*/ native static final boolean resolveAttrs(long theme,
-            int defStyleAttr, int defStyleRes, int[] inValues,
-            int[] inAttrs, int[] outValues, int[] outIndices);
-    /*package*/ native final boolean retrieveAttributes(
-            long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices);
-    /*package*/ native final int getArraySize(int resource);
-    /*package*/ native final int retrieveArray(int resource, int[] outValues);
-    private native final int getStringBlockCount();
-    private native final long getNativeStringBlock(int block);
-
-    /**
-     * {@hide}
-     */
-    public native final String getCookieName(int cookie);
-
-    /**
-     * {@hide}
-     */
-    public native final SparseArray<String> getAssignedPackageIdentifiers();
-
-    /**
-     * {@hide}
-     */
-    public native static final int getGlobalAssetCount();
-    
-    /**
-     * {@hide}
-     */
-    public native static final String getAssetAllocations();
-    
-    /**
-     * {@hide}
-     */
-    public native static final int getGlobalAssetManagerCount();
-    
-    private native final long newTheme();
-    private native final void deleteTheme(long theme);
-    /*package*/ native static final void applyThemeStyle(long theme, int styleRes, boolean force);
-    /*package*/ native static final void copyTheme(long dest, long source);
-    /*package*/ native static final void clearTheme(long theme);
-    /*package*/ native static final int loadThemeAttributeValue(long theme, int ident,
-                                                                TypedValue outValue,
-                                                                boolean resolve);
-    /*package*/ native static final void dumpTheme(long theme, int priority, String tag, String prefix);
-    /*package*/ native static final @NativeConfig int getThemeChangingConfigurations(long theme);
-
-    private native final long openXmlAssetNative(int cookie, String fileName);
-
-    private native final String[] getArrayStringResource(int arrayRes);
-    private native final int[] getArrayStringInfo(int arrayRes);
-    /*package*/ native final int[] getArrayIntResource(int arrayRes);
-    /*package*/ native final int[] getStyleAttributes(int themeRes);
-
-    private native final void init(boolean isSystem);
-    private native final void destroy();
-
-    private final void incRefsLocked(long id) {
+    private void incRefsLocked(long id) {
         if (DEBUG_REFS) {
             if (mRefStacks == null) {
-                mRefStacks = new HashMap<Long, RuntimeException>();
+                mRefStacks = new HashMap<>();
             }
             RuntimeException ex = new RuntimeException();
             ex.fillInStackTrace();
@@ -926,16 +1200,118 @@
         }
         mNumRefs++;
     }
-    
-    private final void decRefsLocked(long id) {
+
+    private void decRefsLocked(long id) {
         if (DEBUG_REFS && mRefStacks != null) {
             mRefStacks.remove(id);
         }
         mNumRefs--;
-        //System.out.println("Dec streams: mNumRefs=" + mNumRefs
-        //                   + " mReleased=" + mReleased);
-        if (mNumRefs == 0) {
-            destroy();
+        if (mNumRefs == 0 && mObject != 0) {
+            nativeDestroy(mObject);
+            mObject = 0;
+            mApkAssets = sEmptyApkAssets;
         }
     }
+
+    // AssetManager setup native methods.
+    private static native long nativeCreate();
+    private static native void nativeDestroy(long ptr);
+    private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
+            boolean invalidateCaches);
+    private static native void nativeSetConfiguration(long ptr, int mcc, int mnc,
+            @Nullable String locale, int orientation, int touchscreen, int density, int keyboard,
+            int keyboardHidden, int navigation, int screenWidth, int screenHeight,
+            int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout,
+            int uiMode, int colorMode, int majorVersion);
+    private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
+            long ptr);
+
+    // File native methods.
+    private static native @Nullable String[] nativeList(long ptr, @NonNull String path)
+            throws IOException;
+    private static native long nativeOpenAsset(long ptr, @NonNull String fileName, int accessMode);
+    private static native @Nullable ParcelFileDescriptor nativeOpenAssetFd(long ptr,
+            @NonNull String fileName, long[] outOffsets) throws IOException;
+    private static native long nativeOpenNonAsset(long ptr, int cookie, @NonNull String fileName,
+            int accessMode);
+    private static native @Nullable ParcelFileDescriptor nativeOpenNonAssetFd(long ptr, int cookie,
+            @NonNull String fileName, @NonNull long[] outOffsets) throws IOException;
+    private static native long nativeOpenXmlAsset(long ptr, int cookie, @NonNull String fileName);
+
+    // Primitive resource native methods.
+    private static native int nativeGetResourceValue(long ptr, @AnyRes int resId, short density,
+            @NonNull TypedValue outValue, boolean resolveReferences);
+    private static native int nativeGetResourceBagValue(long ptr, @AnyRes int resId, int bagEntryId,
+            @NonNull TypedValue outValue);
+
+    private static native @Nullable @AttrRes int[] nativeGetStyleAttributes(long ptr,
+            @StyleRes int resId);
+    private static native @Nullable String[] nativeGetResourceStringArray(long ptr,
+            @ArrayRes int resId);
+    private static native @Nullable int[] nativeGetResourceStringArrayInfo(long ptr,
+            @ArrayRes int resId);
+    private static native @Nullable int[] nativeGetResourceIntArray(long ptr, @ArrayRes int resId);
+    private static native int nativeGetResourceArraySize(long ptr, @ArrayRes int resId);
+    private static native int nativeGetResourceArray(long ptr, @ArrayRes int resId,
+            @NonNull int[] outValues);
+
+    // Resource name/ID native methods.
+    private static native @AnyRes int nativeGetResourceIdentifier(long ptr, @NonNull String name,
+            @Nullable String defType, @Nullable String defPackage);
+    private static native @Nullable String nativeGetResourceName(long ptr, @AnyRes int resid);
+    private static native @Nullable String nativeGetResourcePackageName(long ptr,
+            @AnyRes int resid);
+    private static native @Nullable String nativeGetResourceTypeName(long ptr, @AnyRes int resid);
+    private static native @Nullable String nativeGetResourceEntryName(long ptr, @AnyRes int resid);
+    private static native @Nullable String[] nativeGetLocales(long ptr, boolean excludeSystem);
+    private static native @Nullable Configuration[] nativeGetSizeConfigurations(long ptr);
+
+    // Style attribute retrieval native methods.
+    private static native void nativeApplyStyle(long ptr, long themePtr, @AttrRes int defStyleAttr,
+            @StyleRes int defStyleRes, long xmlParserPtr, @NonNull int[] inAttrs,
+            long outValuesAddress, long outIndicesAddress);
+    private static native boolean nativeResolveAttrs(long ptr, long themePtr,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes, @Nullable int[] inValues,
+            @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices);
+    private static native boolean nativeRetrieveAttributes(long ptr, long xmlParserPtr,
+            @NonNull int[] inAttrs, @NonNull int[] outValues, @NonNull int[] outIndices);
+
+    // Theme related native methods
+    private static native long nativeThemeCreate(long ptr);
+    private static native void nativeThemeDestroy(long themePtr);
+    private static native void nativeThemeApplyStyle(long ptr, long themePtr, @StyleRes int resId,
+            boolean force);
+    static native void nativeThemeCopy(long destThemePtr, long sourceThemePtr);
+    static native void nativeThemeClear(long themePtr);
+    private static native int nativeThemeGetAttributeValue(long ptr, long themePtr,
+            @AttrRes int resId, @NonNull TypedValue outValue, boolean resolve);
+    private static native void nativeThemeDump(long ptr, long themePtr, int priority, String tag,
+            String prefix);
+    static native @NativeConfig int nativeThemeGetChangingConfigurations(long themePtr);
+
+    // AssetInputStream related native methods.
+    private static native void nativeAssetDestroy(long assetPtr);
+    private static native int nativeAssetReadChar(long assetPtr);
+    private static native int nativeAssetRead(long assetPtr, byte[] b, int off, int len);
+    private static native long nativeAssetSeek(long assetPtr, long offset, int whence);
+    private static native long nativeAssetGetLength(long assetPtr);
+    private static native long nativeAssetGetRemainingLength(long assetPtr);
+
+    private static native void nativeVerifySystemIdmaps();
+
+    // Global debug native methods.
+    /**
+     * @hide
+     */
+    public static native int getGlobalAssetCount();
+
+    /**
+     * @hide
+     */
+    public static native String getAssetAllocations();
+
+    /**
+     * @hide
+     */
+    public static native int getGlobalAssetManagerCount();
 }
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index e173653c..8f58891 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -590,7 +590,7 @@
      */
     @NonNull
     public int[] getIntArray(@ArrayRes int id) throws NotFoundException {
-        int[] res = mResourcesImpl.getAssets().getArrayIntResource(id);
+        int[] res = mResourcesImpl.getAssets().getResourceIntArray(id);
         if (res != null) {
             return res;
         }
@@ -613,13 +613,13 @@
     @NonNull
     public TypedArray obtainTypedArray(@ArrayRes int id) throws NotFoundException {
         final ResourcesImpl impl = mResourcesImpl;
-        int len = impl.getAssets().getArraySize(id);
+        int len = impl.getAssets().getResourceArraySize(id);
         if (len < 0) {
             throw new NotFoundException("Array resource ID #0x" + Integer.toHexString(id));
         }
         
         TypedArray array = TypedArray.obtain(this, len);
-        array.mLength = impl.getAssets().retrieveArray(id, array.mData);
+        array.mLength = impl.getAssets().getResourceArray(id, array.mData);
         array.mIndices[0] = 0;
         
         return array;
@@ -1789,8 +1789,7 @@
         // out the attributes from the XML file (applying type information
         // contained in the resources and such).
         XmlBlock.Parser parser = (XmlBlock.Parser)set;
-        mResourcesImpl.getAssets().retrieveAttributes(parser.mParseState, attrs,
-                array.mData, array.mIndices);
+        mResourcesImpl.getAssets().retrieveAttributes(parser, attrs, array.mData, array.mIndices);
 
         array.mXml = parser;
 
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 97cb78b..80e3860 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -27,9 +27,11 @@
 import android.annotation.StyleableRes;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
+import android.content.res.AssetManager.AssetInputStream;
 import android.content.res.Configuration.NativeConfig;
 import android.content.res.Resources.NotFoundException;
 import android.graphics.Bitmap;
+import android.graphics.ImageDecoder;
 import android.graphics.Typeface;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -168,7 +170,6 @@
         mDisplayAdjustments = displayAdjustments;
         mConfiguration.setToDefaults();
         updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
-        mAssets.ensureStringBlocks();
     }
 
     public DisplayAdjustments getDisplayAdjustments() {
@@ -809,8 +810,13 @@
                 } else {
                     final InputStream is = mAssets.openNonAsset(
                             value.assetCookie, file, AssetManager.ACCESS_STREAMING);
-                    dr = Drawable.createFromResourceStream(wrapper, value, is, file, null);
-                    is.close();
+                    AssetInputStream ais = (AssetInputStream) is;
+                    // ImageDecoder will close the input stream.
+                    ImageDecoder.Source src = new ImageDecoder.AssetInputStreamSource(ais,
+                            wrapper, value);
+                    dr = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                        decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                    });
                 }
             } finally {
                 stack.pop();
@@ -1274,8 +1280,7 @@
 
         void applyStyle(int resId, boolean force) {
             synchronized (mKey) {
-                AssetManager.applyThemeStyle(mTheme, resId, force);
-
+                mAssets.applyStyleToTheme(mTheme, resId, force);
                 mThemeResId = resId;
                 mKey.append(resId, force);
             }
@@ -1284,7 +1289,7 @@
         void setTo(ThemeImpl other) {
             synchronized (mKey) {
                 synchronized (other.mKey) {
-                    AssetManager.copyTheme(mTheme, other.mTheme);
+                    AssetManager.nativeThemeCopy(mTheme, other.mTheme);
 
                     mThemeResId = other.mThemeResId;
                     mKey.setTo(other.getKey());
@@ -1307,12 +1312,10 @@
                 // out the attributes from the XML file (applying type information
                 // contained in the resources and such).
                 final XmlBlock.Parser parser = (XmlBlock.Parser) set;
-                AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
-                        parser != null ? parser.mParseState : 0,
-                        attrs, attrs.length, array.mDataAddress, array.mIndicesAddress);
+                mAssets.applyStyle(mTheme, defStyleAttr, defStyleRes, parser, attrs,
+                        array.mDataAddress, array.mIndicesAddress);
                 array.mTheme = wrapper;
                 array.mXml = parser;
-
                 return array;
             }
         }
@@ -1329,7 +1332,7 @@
                 }
 
                 final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
-                AssetManager.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
+                mAssets.resolveAttrs(mTheme, 0, 0, values, attrs, array.mData, array.mIndices);
                 array.mTheme = wrapper;
                 array.mXml = null;
                 return array;
@@ -1349,14 +1352,14 @@
         @Config int getChangingConfigurations() {
             synchronized (mKey) {
                 final @NativeConfig int nativeChangingConfig =
-                        AssetManager.getThemeChangingConfigurations(mTheme);
+                        AssetManager.nativeThemeGetChangingConfigurations(mTheme);
                 return ActivityInfo.activityInfoConfigNativeToJava(nativeChangingConfig);
             }
         }
 
         public void dump(int priority, String tag, String prefix) {
             synchronized (mKey) {
-                AssetManager.dumpTheme(mTheme, priority, tag, prefix);
+                mAssets.dumpTheme(mTheme, priority, tag, prefix);
             }
         }
 
@@ -1385,13 +1388,13 @@
          */
         void rebase() {
             synchronized (mKey) {
-                AssetManager.clearTheme(mTheme);
+                AssetManager.nativeThemeClear(mTheme);
 
                 // Reapply the same styles in the same order.
                 for (int i = 0; i < mKey.mCount; i++) {
                     final int resId = mKey.mResId[i];
                     final boolean force = mKey.mForce[i];
-                    AssetManager.applyThemeStyle(mTheme, resId, force);
+                    mAssets.applyStyleToTheme(mTheme, resId, force);
                 }
             }
         }
diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java
index f33c751..cbb3c6d 100644
--- a/core/java/android/content/res/TypedArray.java
+++ b/core/java/android/content/res/TypedArray.java
@@ -61,6 +61,15 @@
         return attrs;
     }
 
+    // STYLE_ prefixed constants are offsets within the typed data array.
+    static final int STYLE_NUM_ENTRIES = 6;
+    static final int STYLE_TYPE = 0;
+    static final int STYLE_DATA = 1;
+    static final int STYLE_ASSET_COOKIE = 2;
+    static final int STYLE_RESOURCE_ID = 3;
+    static final int STYLE_CHANGING_CONFIGURATIONS = 4;
+    static final int STYLE_DENSITY = 5;
+
     private final Resources mResources;
     private DisplayMetrics mMetrics;
     private AssetManager mAssets;
@@ -78,7 +87,7 @@
 
     private void resize(int len) {
         mLength = len;
-        final int dataLen = len * AssetManager.STYLE_NUM_ENTRIES;
+        final int dataLen = len * STYLE_NUM_ENTRIES;
         final int indicesLen = len + 1;
         final VMRuntime runtime = VMRuntime.getRuntime();
         if (mDataAddress == 0 || mData.length < dataLen) {
@@ -166,9 +175,9 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return null;
         } else if (type == TypedValue.TYPE_STRING) {
@@ -203,9 +212,9 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return null;
         } else if (type == TypedValue.TYPE_STRING) {
@@ -242,14 +251,13 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_STRING) {
-            final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+            final int cookie = data[index + STYLE_ASSET_COOKIE];
             if (cookie < 0) {
-                return mXml.getPooledString(
-                    data[index+AssetManager.STYLE_DATA]).toString();
+                return mXml.getPooledString(data[index + STYLE_DATA]).toString();
             }
         }
         return null;
@@ -274,11 +282,11 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava(
-                data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
+                data[index + STYLE_CHANGING_CONFIGURATIONS]);
         if ((changingConfigs & ~allowedChangingConfigs) != 0) {
             return null;
         }
@@ -320,14 +328,14 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return defValue;
         } else if (type >= TypedValue.TYPE_FIRST_INT
                 && type <= TypedValue.TYPE_LAST_INT) {
-            return data[index+AssetManager.STYLE_DATA] != 0;
+            return data[index + STYLE_DATA] != 0;
         }
 
         final TypedValue v = mValue;
@@ -359,14 +367,14 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return defValue;
         } else if (type >= TypedValue.TYPE_FIRST_INT
                 && type <= TypedValue.TYPE_LAST_INT) {
-            return data[index+AssetManager.STYLE_DATA];
+            return data[index + STYLE_DATA];
         }
 
         final TypedValue v = mValue;
@@ -396,16 +404,16 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return defValue;
         } else if (type == TypedValue.TYPE_FLOAT) {
-            return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]);
+            return Float.intBitsToFloat(data[index + STYLE_DATA]);
         } else if (type >= TypedValue.TYPE_FIRST_INT
                 && type <= TypedValue.TYPE_LAST_INT) {
-            return data[index+AssetManager.STYLE_DATA];
+            return data[index + STYLE_DATA];
         }
 
         final TypedValue v = mValue;
@@ -446,15 +454,15 @@
         }
 
         final int attrIndex = index;
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
 
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return defValue;
         } else if (type >= TypedValue.TYPE_FIRST_INT
                 && type <= TypedValue.TYPE_LAST_INT) {
-            return data[index+AssetManager.STYLE_DATA];
+            return data[index + STYLE_DATA];
         } else if (type == TypedValue.TYPE_STRING) {
             final TypedValue value = mValue;
             if (getValueAt(index, value)) {
@@ -498,7 +506,7 @@
         }
 
         final TypedValue value = mValue;
-        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+        if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                 throw new UnsupportedOperationException(
                         "Failed to resolve attribute at index " + index + ": " + value);
@@ -533,7 +541,7 @@
         }
 
         final TypedValue value = mValue;
-        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+        if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                 throw new UnsupportedOperationException(
                         "Failed to resolve attribute at index " + index + ": " + value);
@@ -564,15 +572,15 @@
         }
 
         final int attrIndex = index;
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
 
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return defValue;
         } else if (type >= TypedValue.TYPE_FIRST_INT
                 && type <= TypedValue.TYPE_LAST_INT) {
-            return data[index+AssetManager.STYLE_DATA];
+            return data[index + STYLE_DATA];
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
             final TypedValue value = mValue;
             getValueAt(index, value);
@@ -612,15 +620,14 @@
         }
 
         final int attrIndex = index;
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
 
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return defValue;
         } else if (type == TypedValue.TYPE_DIMENSION) {
-            return TypedValue.complexToDimension(
-                    data[index + AssetManager.STYLE_DATA], mMetrics);
+            return TypedValue.complexToDimension(data[index + STYLE_DATA], mMetrics);
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
             final TypedValue value = mValue;
             getValueAt(index, value);
@@ -661,15 +668,14 @@
         }
 
         final int attrIndex = index;
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
 
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return defValue;
         } else if (type == TypedValue.TYPE_DIMENSION) {
-            return TypedValue.complexToDimensionPixelOffset(
-                    data[index + AssetManager.STYLE_DATA], mMetrics);
+            return TypedValue.complexToDimensionPixelOffset(data[index + STYLE_DATA], mMetrics);
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
             final TypedValue value = mValue;
             getValueAt(index, value);
@@ -711,15 +717,14 @@
         }
 
         final int attrIndex = index;
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
 
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return defValue;
         } else if (type == TypedValue.TYPE_DIMENSION) {
-            return TypedValue.complexToDimensionPixelSize(
-                data[index+AssetManager.STYLE_DATA], mMetrics);
+            return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
             final TypedValue value = mValue;
             getValueAt(index, value);
@@ -755,16 +760,15 @@
         }
 
         final int attrIndex = index;
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
 
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type >= TypedValue.TYPE_FIRST_INT
                 && type <= TypedValue.TYPE_LAST_INT) {
-            return data[index+AssetManager.STYLE_DATA];
+            return data[index + STYLE_DATA];
         } else if (type == TypedValue.TYPE_DIMENSION) {
-            return TypedValue.complexToDimensionPixelSize(
-                data[index+AssetManager.STYLE_DATA], mMetrics);
+            return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
             final TypedValue value = mValue;
             getValueAt(index, value);
@@ -795,15 +799,14 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type >= TypedValue.TYPE_FIRST_INT
                 && type <= TypedValue.TYPE_LAST_INT) {
-            return data[index+AssetManager.STYLE_DATA];
+            return data[index + STYLE_DATA];
         } else if (type == TypedValue.TYPE_DIMENSION) {
-            return TypedValue.complexToDimensionPixelSize(
-                    data[index + AssetManager.STYLE_DATA], mMetrics);
+            return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
         }
 
         return defValue;
@@ -833,15 +836,14 @@
         }
 
         final int attrIndex = index;
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
 
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return defValue;
         } else if (type == TypedValue.TYPE_FRACTION) {
-            return TypedValue.complexToFraction(
-                data[index+AssetManager.STYLE_DATA], base, pbase);
+            return TypedValue.complexToFraction(data[index + STYLE_DATA], base, pbase);
         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
             final TypedValue value = mValue;
             getValueAt(index, value);
@@ -874,10 +876,10 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) {
-            final int resid = data[index+AssetManager.STYLE_RESOURCE_ID];
+        if (data[index + STYLE_TYPE] != TypedValue.TYPE_NULL) {
+            final int resid = data[index + STYLE_RESOURCE_ID];
             if (resid != 0) {
                 return resid;
             }
@@ -902,10 +904,10 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
-            return data[index + AssetManager.STYLE_DATA];
+        if (data[index + STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
+            return data[index + STYLE_DATA];
         }
         return defValue;
     }
@@ -939,7 +941,7 @@
         }
 
         final TypedValue value = mValue;
-        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+        if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                 throw new UnsupportedOperationException(
                         "Failed to resolve attribute at index " + index + ": " + value);
@@ -975,7 +977,7 @@
         }
 
         final TypedValue value = mValue;
-        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+        if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
                 throw new UnsupportedOperationException(
                         "Failed to resolve attribute at index " + index + ": " + value);
@@ -1006,7 +1008,7 @@
         }
 
         final TypedValue value = mValue;
-        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+        if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
             return mResources.getTextArray(value.resourceId);
         }
         return null;
@@ -1027,7 +1029,7 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue);
+        return getValueAt(index * STYLE_NUM_ENTRIES, outValue);
     }
 
     /**
@@ -1043,8 +1045,8 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
-        return mData[index + AssetManager.STYLE_TYPE];
+        index *= STYLE_NUM_ENTRIES;
+        return mData[index + STYLE_TYPE];
     }
 
     /**
@@ -1063,9 +1065,9 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         return type != TypedValue.TYPE_NULL;
     }
 
@@ -1084,11 +1086,11 @@
             throw new RuntimeException("Cannot make calls to a recycled instance!");
         }
 
-        index *= AssetManager.STYLE_NUM_ENTRIES;
+        index *= STYLE_NUM_ENTRIES;
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         return type != TypedValue.TYPE_NULL
-                || data[index+AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
+                || data[index + STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
     }
 
     /**
@@ -1109,7 +1111,7 @@
         }
 
         final TypedValue value = mValue;
-        if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
+        if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
             return value;
         }
         return null;
@@ -1181,16 +1183,16 @@
         final int[] data = mData;
         final int N = length();
         for (int i = 0; i < N; i++) {
-            final int index = i * AssetManager.STYLE_NUM_ENTRIES;
-            if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
+            final int index = i * STYLE_NUM_ENTRIES;
+            if (data[index + STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
                 // Not an attribute, ignore.
                 continue;
             }
 
             // Null the entry so that we can safely call getZzz().
-            data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL;
+            data[index + STYLE_TYPE] = TypedValue.TYPE_NULL;
 
-            final int attr = data[index + AssetManager.STYLE_DATA];
+            final int attr = data[index + STYLE_DATA];
             if (attr == 0) {
                 // Useless data, ignore.
                 continue;
@@ -1231,45 +1233,44 @@
         final int[] data = mData;
         final int N = length();
         for (int i = 0; i < N; i++) {
-            final int index = i * AssetManager.STYLE_NUM_ENTRIES;
-            final int type = data[index + AssetManager.STYLE_TYPE];
+            final int index = i * STYLE_NUM_ENTRIES;
+            final int type = data[index + STYLE_TYPE];
             if (type == TypedValue.TYPE_NULL) {
                 continue;
             }
             changingConfig |= ActivityInfo.activityInfoConfigNativeToJava(
-                    data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
+                    data[index + STYLE_CHANGING_CONFIGURATIONS]);
         }
         return changingConfig;
     }
 
     private boolean getValueAt(int index, TypedValue outValue) {
         final int[] data = mData;
-        final int type = data[index+AssetManager.STYLE_TYPE];
+        final int type = data[index + STYLE_TYPE];
         if (type == TypedValue.TYPE_NULL) {
             return false;
         }
         outValue.type = type;
-        outValue.data = data[index+AssetManager.STYLE_DATA];
-        outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
-        outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID];
+        outValue.data = data[index + STYLE_DATA];
+        outValue.assetCookie = data[index + STYLE_ASSET_COOKIE];
+        outValue.resourceId = data[index + STYLE_RESOURCE_ID];
         outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
-                data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
-        outValue.density = data[index+AssetManager.STYLE_DENSITY];
+                data[index + STYLE_CHANGING_CONFIGURATIONS]);
+        outValue.density = data[index + STYLE_DENSITY];
         outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;
         return true;
     }
 
     private CharSequence loadStringValueAt(int index) {
         final int[] data = mData;
-        final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
+        final int cookie = data[index + STYLE_ASSET_COOKIE];
         if (cookie < 0) {
             if (mXml != null) {
-                return mXml.getPooledString(
-                    data[index+AssetManager.STYLE_DATA]);
+                return mXml.getPooledString(data[index + STYLE_DATA]);
             }
             return null;
         }
-        return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]);
+        return mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]);
     }
 
     /** @hide */
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index e6b95741..d4ccffb 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -16,6 +16,7 @@
 
 package android.content.res;
 
+import android.annotation.Nullable;
 import android.util.TypedValue;
 
 import com.android.internal.util.XmlUtils;
@@ -33,7 +34,7 @@
  * 
  * {@hide}
  */
-final class XmlBlock {
+final class XmlBlock implements AutoCloseable {
     private static final boolean DEBUG=false;
 
     public XmlBlock(byte[] data) {
@@ -48,6 +49,7 @@
         mStrings = new StringBlock(nativeGetStringBlock(mNative), false);
     }
 
+    @Override
     public void close() {
         synchronized (this) {
             if (mOpen) {
@@ -478,13 +480,13 @@
      *  are doing!  The given native object must exist for the entire lifetime
      *  of this newly creating XmlBlock.
      */
-    XmlBlock(AssetManager assets, long xmlBlock) {
+    XmlBlock(@Nullable AssetManager assets, long xmlBlock) {
         mAssets = assets;
         mNative = xmlBlock;
         mStrings = new StringBlock(nativeGetStringBlock(xmlBlock), false);
     }
 
-    private final AssetManager mAssets;
+    private @Nullable final AssetManager mAssets;
     private final long mNative;
     /*package*/ final StringBlock mStrings;
     private boolean mOpen = true;
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index c1c0812..ae1f57d 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2006,7 +2006,6 @@
      *     SQLiteDatabase db = SQLiteDatabase.openDatabase("db_filename", cursorFactory,
      *             SQLiteDatabase.CREATE_IF_NECESSARY | SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING,
      *             myDatabaseErrorHandler);
-     *     db.enableWriteAheadLogging();
      * </pre></code>
      * </p><p>
      * Another way to enable write-ahead logging is to call {@link #enableWriteAheadLogging}
diff --git a/core/java/android/os/IHwBinder.java b/core/java/android/os/IHwBinder.java
index a565dee..fbdf27e 100644
--- a/core/java/android/os/IHwBinder.java
+++ b/core/java/android/os/IHwBinder.java
@@ -21,12 +21,6 @@
 /** @hide */
 @SystemApi
 public interface IHwBinder {
-    // These MUST match their corresponding libhwbinder/IBinder.h definition !!!
-    /** @hide */
-    public static final int FIRST_CALL_TRANSACTION = 1;
-    /** @hide */
-    public static final int FLAG_ONEWAY = 1;
-
     /**
      * Process a hwbinder transaction.
      *
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 2093cec..7e7af1a 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -943,6 +943,20 @@
      * @see #getUserRestrictions()
      */
     public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
+
+    /**
+     * Specifies whether the user is allowed to print.
+     *
+     * This restriction can be set by device or profile owner.
+     *
+     * The default value is {@code false}.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_PRINTING = "no_printing";
+
     /**
      * Application restriction key that is used to indicate the pending arrival
      * of real restrictions for the app.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index e381693..b6fab1e 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10333,6 +10333,15 @@
         public static final String FPS_DEVISOR = "fps_divisor";
 
         /**
+         * Flag to enable or disable display panel low power mode (lpm)
+         * false -> Display panel power saving mode is disabled.
+         * true  -> Display panel power saving mode is enabled.
+         *
+         * @hide
+         */
+        public static final String DISPLAY_PANEL_LPM = "display_panel_lpm";
+
+        /**
          * App standby (app idle) specific settings.
          * This is encoded as a key=value list, separated by commas. Ex:
          * <p>
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 273f097..8e60a72 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3971,15 +3971,7 @@
     }
 
     /**
-     * Layout debugging code which draws rectangles around layout params.
-     *
-     * <p>This function is called automatically when the developer setting is enabled.<p/>
-     *
-     * <p>It is strongly advised to only call this function from debug builds as there is
-     * a risk of leaking unwanted layout information.<p/>
-     *
-     * @param canvas the canvas on which to draw
-     * @param paint the paint used to draw through
+     * @hide
      */
     protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
         for (int i = 0; i < getChildCount(); i++) {
@@ -3989,19 +3981,7 @@
     }
 
     /**
-     * Layout debugging code which draws rectangles around:
-     * <ul>
-     *     <li>optical bounds<li/>
-     *     <li>margins<li/>
-     *     <li>clip bounds<li/>
-     * <ul/>
-     *
-     * <p>This function is called automatically when the developer setting is enabled.<p/>
-     *
-     * <p>It is strongly advised to only call this function from debug builds as there is
-     * a risk of leaking unwanted layout information.<p/>
-     *
-     * @param canvas the canvas on which to draw
+     * @hide
      */
     protected void onDebugDraw(Canvas canvas) {
         Paint paint = getDebugPaint();
@@ -7732,14 +7712,10 @@
         /**
          * Use {@code canvas} to draw suitable debugging annotations for these LayoutParameters.
          *
-         * <p>This function is called automatically when the developer setting is enabled.<p/>
-         *
-         * <p>It is strongly advised to only call this function from debug builds as there is
-         * a risk of leaking unwanted layout information.<p/>
-         *
          * @param view the view that contains these layout parameters
          * @param canvas the canvas on which to draw
-         * @param paint the paint used to draw through
+         *
+         * @hide
          */
         public void onDebugDraw(View view, Canvas canvas, Paint paint) {
         }
@@ -8243,6 +8219,9 @@
             return ((mMarginFlags & LAYOUT_DIRECTION_MASK) == View.LAYOUT_DIRECTION_RTL);
         }
 
+        /**
+         * @hide
+         */
         @Override
         public void onDebugDraw(View view, Canvas canvas, Paint paint) {
             Insets oi = isLayoutModeOptical(view.mParent) ? view.getOpticalInsets() : Insets.NONE;
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 5ab579d..41ceb30 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -1299,6 +1299,16 @@
             if (mSelectionModifierCursorController != null) {
                 mSelectionModifierCursorController.resetTouchOffsets();
             }
+
+            ensureNoSelectionIfNonSelectable();
+        }
+    }
+
+    private void ensureNoSelectionIfNonSelectable() {
+        // This could be the case if a TextLink has been tapped.
+        if (!mTextView.textCanBeSelected() && mTextView.hasSelection()) {
+            Selection.setSelection((Spannable) mTextView.getText(),
+                    mTextView.length(), mTextView.length());
         }
     }
 
@@ -1382,6 +1392,8 @@
 
             // Don't leave us in the middle of a batch edit. Same as in onFocusChanged
             ensureEndedBatchEdit();
+
+            ensureNoSelectionIfNonSelectable();
         }
     }
 
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index 012b918..3aae849 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -904,6 +904,9 @@
         }
     }
 
+    /**
+     * @hide
+     */
     @Override
     protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
         // Apply defaults, so as to remove UNDEFINED values
@@ -919,6 +922,9 @@
         }
     }
 
+    /**
+     * @hide
+     */
     @Override
     protected void onDebugDraw(Canvas canvas) {
         Paint paint = new Paint();
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 7a4c800..8836561 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -18,8 +18,10 @@
 
 import android.annotation.FloatRange;
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.annotation.UiThread;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -269,4 +271,46 @@
         return mWindow.getContentView().findViewById(
                 com.android.internal.R.id.magnifier_image);
     }
+
+    /**
+     * @return the content being currently displayed in the magnifier, as bitmap
+     *
+     * @hide
+     */
+    @TestApi
+    public Bitmap getContent() {
+        return mBitmap;
+    }
+
+    /**
+     * @return the position of the magnifier window relative to the screen
+     *
+     * @hide
+     */
+    @TestApi
+    public Rect getWindowPositionOnScreen() {
+        final int[] viewLocationOnScreen = new int[2];
+        mView.getLocationOnScreen(viewLocationOnScreen);
+        final int[] viewLocationInSurface = new int[2];
+        mView.getLocationInSurface(viewLocationInSurface);
+
+        final int left = mWindowCoords.x + viewLocationOnScreen[0] - viewLocationInSurface[0];
+        final int top = mWindowCoords.y + viewLocationOnScreen[1] - viewLocationInSurface[1];
+        return new Rect(left, top, left + mWindow.getWidth(), top + mWindow.getHeight());
+    }
+
+    /**
+     * @return the size of the magnifier window in dp
+     *
+     * @hide
+     */
+    @TestApi
+    public static PointF getMagnifierDefaultSize() {
+        final Resources resources = Resources.getSystem();
+        final float density = resources.getDisplayMetrics().density;
+        final PointF size = new PointF();
+        size.x = resources.getDimension(com.android.internal.R.dimen.magnifier_width) / density;
+        size.y = resources.getDimension(com.android.internal.R.dimen.magnifier_height) / density;
+        return size;
+    }
 }
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
new file mode 100644
index 0000000..71d3bb5
--- /dev/null
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A class to extract Bitmaps from a MessagingStyle message.
+ */
+public class LocalImageResolver {
+
+    private static final int MAX_SAFE_ICON_SIZE_PX = 480;
+
+    @Nullable
+    public static Drawable resolveImage(Uri uri, Context context) throws IOException {
+        BitmapFactory.Options onlyBoundsOptions = getBoundsOptionsForImage(uri, context);
+        if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
+            return null;
+        }
+
+        int originalSize =
+                (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth)
+                        ? onlyBoundsOptions.outHeight
+                        : onlyBoundsOptions.outWidth;
+
+        double ratio = (originalSize > MAX_SAFE_ICON_SIZE_PX)
+                        ? (originalSize / MAX_SAFE_ICON_SIZE_PX)
+                        : 1.0;
+
+        BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
+        bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
+        InputStream input = context.getContentResolver().openInputStream(uri);
+        Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
+        input.close();
+        return new BitmapDrawable(context.getResources(), bitmap);
+    }
+
+    private static BitmapFactory.Options getBoundsOptionsForImage(Uri uri, Context context)
+            throws IOException {
+        InputStream input = context.getContentResolver().openInputStream(uri);
+        BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
+        onlyBoundsOptions.inJustDecodeBounds = true;
+        BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
+        input.close();
+        return onlyBoundsOptions;
+    }
+
+    private static int getPowerOfTwoForSampleRatio(double ratio) {
+        int k = Integer.highestOneBit((int) Math.floor(ratio));
+        return Math.max(1, k);
+    }
+}
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 5577d6e..239beaa 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -22,9 +22,12 @@
 import android.annotation.StyleRes;
 import android.app.Notification;
 import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.Icon;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.util.Pools;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -61,6 +64,11 @@
     private boolean mIsHidingAnimated;
     private boolean mNeedsGeneratedAvatar;
     private Notification.Person mSender;
+    private boolean mAvatarsAtEnd;
+    private ViewGroup mImageContainer;
+    private MessagingImageMessage mIsolatedMessage;
+    private boolean mTransformingImages;
+    private Point mDisplaySize = new Point();
 
     public MessagingGroup(@NonNull Context context) {
         super(context);
@@ -87,6 +95,35 @@
         mSenderName = findViewById(R.id.message_name);
         mSenderName.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
         mAvatarView = findViewById(R.id.message_icon);
+        mImageContainer = findViewById(R.id.messaging_group_icon_container);
+        DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
+        mDisplaySize.x = displayMetrics.widthPixels;
+        mDisplaySize.y = displayMetrics.heightPixels;
+    }
+
+    public void updateClipRect() {
+        // We want to clip to the senderName if it's available, otherwise our images will come
+        // from a weird position
+        Rect clipRect;
+        if (mSenderName.getVisibility() != View.GONE && !mTransformingImages) {
+            ViewGroup parent = (ViewGroup) mSenderName.getParent();
+            int top = getDistanceFromParent(mSenderName, parent) - getDistanceFromParent(
+                    mMessageContainer, parent) + mSenderName.getHeight();
+            clipRect = new Rect(0, top, mDisplaySize.x, mDisplaySize.y);
+        } else {
+            clipRect = null;
+        }
+        mMessageContainer.setClipBounds(clipRect);
+    }
+
+    private int getDistanceFromParent(View searchedView, ViewGroup parent) {
+        int position = 0;
+        View view = searchedView;
+        while(view != parent) {
+            position += view.getTop() + view.getTranslationY();
+            view = (View) view.getParent();
+        }
+        return position;
     }
 
     public void setSender(Notification.Person sender, CharSequence nameOverride) {
@@ -129,12 +166,14 @@
     }
 
     public void removeMessage(MessagingMessage messagingMessage) {
-        mMessageContainer.removeView(messagingMessage);
+        ViewGroup messageParent = (ViewGroup) messagingMessage.getView().getParent();
+        messageParent.removeView(messagingMessage.getView());
         Runnable recycleRunnable = () -> {
-            mMessageContainer.removeTransientView(messagingMessage);
+            messageParent.removeTransientView(messagingMessage.getView());
             messagingMessage.recycle();
             if (mMessageContainer.getChildCount() == 0
-                    && mMessageContainer.getTransientViewCount() == 0) {
+                    && mMessageContainer.getTransientViewCount() == 0
+                    && mImageContainer.getChildCount() == 0) {
                 ViewParent parent = getParent();
                 if (parent instanceof ViewGroup) {
                     ((ViewGroup) parent).removeView(MessagingGroup.this);
@@ -148,9 +187,10 @@
             }
         };
         if (isShown()) {
-            mMessageContainer.addTransientView(messagingMessage, 0);
-            performRemoveAnimation(messagingMessage, recycleRunnable);
-            if (mMessageContainer.getChildCount() == 0) {
+            messageParent.addTransientView(messagingMessage.getView(), 0);
+            performRemoveAnimation(messagingMessage.getView(), recycleRunnable);
+            if (mMessageContainer.getChildCount() == 0
+                    && mImageContainer.getChildCount() == 0) {
                 removeGroupAnimated(null);
             }
         } else {
@@ -160,12 +200,8 @@
     }
 
     private void removeGroupAnimated(Runnable endAction) {
-        MessagingPropertyAnimator.fadeOut(mAvatarView, null);
-        MessagingPropertyAnimator.startLocalTranslationTo(mAvatarView,
-                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
-        MessagingPropertyAnimator.fadeOut(mSenderName, null);
-        MessagingPropertyAnimator.startLocalTranslationTo(mSenderName,
-                (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
+        performRemoveAnimation(mAvatarView, null);
+        performRemoveAnimation(mSenderName, null);
         boolean endActionTriggered = false;
         for (int i = mMessageContainer.getChildCount() - 1; i >= 0; i--) {
             View child = mMessageContainer.getChildAt(i);
@@ -182,14 +218,17 @@
             performRemoveAnimation(child, childEndAction);
             endActionTriggered = true;
         }
+        if (mIsolatedMessage != null) {
+            performRemoveAnimation(mIsolatedMessage, !endActionTriggered ? endAction : null);
+            endActionTriggered = true;
+        }
         if (!endActionTriggered && endAction != null) {
             endAction.run();
         }
     }
 
-    public void performRemoveAnimation(View message,
-            Runnable recycleRunnable) {
-        MessagingPropertyAnimator.fadeOut(message, recycleRunnable);
+    public void performRemoveAnimation(View message, Runnable endAction) {
+        MessagingPropertyAnimator.fadeOut(message, endAction);
         MessagingPropertyAnimator.startLocalTranslationTo(message,
                 (int) (-getHeight() * 0.5f), MessagingLayout.FAST_OUT_LINEAR_IN);
     }
@@ -222,6 +261,9 @@
                 }
             }
         }
+        if (mMessageContainer.getChildCount() == 0 && mIsolatedMessage != null) {
+            return mIsolatedMessage.getMeasuredType();
+        }
         return MEASURED_NORMAL;
     }
 
@@ -234,6 +276,7 @@
                 result += ((MessagingLinearLayout.MessagingChild) child).getConsumedLines();
             }
         }
+        result = mIsolatedMessage != null ? Math.max(result, 1) : result;
         // A group is usually taking up quite some space with the padding and the name, let's add 1
         return result + 1;
     }
@@ -289,26 +332,67 @@
 
     public void setMessages(List<MessagingMessage> group) {
         // Let's now make sure all children are added and in the correct order
+        int textMessageIndex = 0;
+        MessagingImageMessage isolatedMessage = null;
         for (int messageIndex = 0; messageIndex < group.size(); messageIndex++) {
             MessagingMessage message = group.get(messageIndex);
+            message.setColor(mTextColor);
             if (message.getGroup() != this) {
                 message.setMessagingGroup(this);
-                ViewParent parent = mMessageContainer.getParent();
-                if (parent instanceof ViewGroup) {
-                    ((ViewGroup) parent).removeView(message);
-                }
-                mMessageContainer.addView(message, messageIndex);
                 mAddedMessages.add(message);
             }
-            if (messageIndex != mMessageContainer.indexOfChild(message)) {
-                mMessageContainer.removeView(message);
-                mMessageContainer.addView(message, messageIndex);
+            boolean isImage = message instanceof MessagingImageMessage;
+            if (mAvatarsAtEnd && isImage) {
+                isolatedMessage = (MessagingImageMessage) message;
+            } else {
+                if (removeFromParentIfDifferent(message, mMessageContainer)) {
+                    ViewGroup.LayoutParams layoutParams = message.getView().getLayoutParams();
+                    if (layoutParams != null
+                            && !(layoutParams instanceof MessagingLinearLayout.LayoutParams)) {
+                        message.getView().setLayoutParams(
+                                mMessageContainer.generateDefaultLayoutParams());
+                    }
+                    mMessageContainer.addView(message.getView(), textMessageIndex);
+                }
+                if (isImage) {
+                    ((MessagingImageMessage) message).setIsolated(false);
+                }
+                // Let's sort them properly
+                if (textMessageIndex != mMessageContainer.indexOfChild(message.getView())) {
+                    mMessageContainer.removeView(message.getView());
+                    mMessageContainer.addView(message.getView(), textMessageIndex);
+                }
+                textMessageIndex++;
             }
-            message.setTextColor(mTextColor);
         }
+        if (isolatedMessage != null) {
+            if (removeFromParentIfDifferent(isolatedMessage, mImageContainer)) {
+                mImageContainer.removeAllViews();
+                mImageContainer.addView(isolatedMessage.getView());
+            }
+            isolatedMessage.setIsolated(true);
+        } else if (mIsolatedMessage != null) {
+            mImageContainer.removeAllViews();
+        }
+        mIsolatedMessage = isolatedMessage;
         mMessages = group;
     }
 
+    /**
+     * Remove the message from the parent if the parent isn't the one provided
+     * @return whether the message was removed
+     */
+    private boolean removeFromParentIfDifferent(MessagingMessage message, ViewGroup newParent) {
+        ViewParent parent = message.getView().getParent();
+        if (parent != newParent) {
+            if (parent instanceof ViewGroup) {
+                ((ViewGroup) parent).removeView(message.getView());
+            }
+            return true;
+        }
+        return false;
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
@@ -317,13 +401,14 @@
                 @Override
                 public boolean onPreDraw() {
                     for (MessagingMessage message : mAddedMessages) {
-                        if (!message.isShown()) {
+                        if (!message.getView().isShown()) {
                             continue;
                         }
-                        MessagingPropertyAnimator.fadeIn(message);
+                        MessagingPropertyAnimator.fadeIn(message.getView());
                         if (!mFirstLayout) {
-                            MessagingPropertyAnimator.startLocalTranslationFrom(message,
-                                    message.getHeight(), MessagingLayout.LINEAR_OUT_SLOW_IN);
+                            MessagingPropertyAnimator.startLocalTranslationFrom(message.getView(),
+                                    message.getView().getHeight(),
+                                    MessagingLayout.LINEAR_OUT_SLOW_IN);
                         }
                     }
                     mAddedMessages.clear();
@@ -333,6 +418,7 @@
             });
         }
         mFirstLayout = false;
+        updateClipRect();
     }
 
     /**
@@ -372,6 +458,10 @@
         return mMessageContainer;
     }
 
+    public MessagingImageMessage getIsolatedMessage() {
+        return mIsolatedMessage;
+    }
+
     public boolean needsGeneratedAvatar() {
         return mNeedsGeneratedAvatar;
     }
@@ -379,4 +469,19 @@
     public Notification.Person getSender() {
         return mSender;
     }
+
+    public void setTransformingImages(boolean transformingImages) {
+        mTransformingImages = transformingImages;
+    }
+
+    public void setDisplayAvatarsAtEnd(boolean atEnd) {
+        if (mAvatarsAtEnd != atEnd) {
+            mAvatarsAtEnd = atEnd;
+            mImageContainer.setVisibility(atEnd ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    public List<MessagingMessage> getMessages() {
+        return mMessages;
+    }
 }
diff --git a/core/java/com/android/internal/widget/MessagingImageMessage.java b/core/java/com/android/internal/widget/MessagingImageMessage.java
new file mode 100644
index 0000000..961f90a
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingImageMessage.java
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Pools;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.io.IOException;
+
+/**
+ * A message of a {@link MessagingLayout} that is an image.
+ */
+@RemoteViews.RemoteView
+public class MessagingImageMessage extends ImageView implements MessagingMessage {
+    private static final String TAG = "MessagingImageMessage";
+    private static Pools.SimplePool<MessagingImageMessage> sInstancePool
+            = new Pools.SynchronizedPool<>(10);
+    private final MessagingMessageState mState = new MessagingMessageState(this);
+    private final int mMinImageHeight;
+    private final Path mPath = new Path();
+    private final int mImageRounding;
+    private final int mMaxImageHeight;
+    private final int mIsolatedSize;
+    private final int mExtraSpacing;
+    private Drawable mDrawable;
+    private float mAspectRatio;
+    private int mActualWidth;
+    private int mActualHeight;
+    private boolean mIsIsolated;
+
+    public MessagingImageMessage(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        this(context, attrs, defStyleAttr, 0);
+    }
+
+    public MessagingImageMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        mMinImageHeight = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.messaging_image_min_size);
+        mMaxImageHeight = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.messaging_image_max_height);
+        mImageRounding = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.messaging_image_rounding);
+        mExtraSpacing = context.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.messaging_image_extra_spacing);
+        setMaxHeight(mMaxImageHeight);
+        mIsolatedSize = getResources().getDimensionPixelSize(R.dimen.messaging_avatar_size);
+    }
+
+    @Override
+    public MessagingMessageState getState() {
+        return mState;
+    }
+
+    @Override
+    public boolean setMessage(Notification.MessagingStyle.Message message) {
+        MessagingMessage.super.setMessage(message);
+        Drawable drawable;
+        try {
+            drawable = LocalImageResolver.resolveImage(message.getDataUri(), getContext());
+        } catch (IOException e) {
+            e.printStackTrace();
+            return false;
+        }
+        int intrinsicHeight = drawable.getIntrinsicHeight();
+        if (intrinsicHeight == 0) {
+            Log.w(TAG, "Drawable with 0 intrinsic height was returned");
+            return false;
+        }
+        mDrawable = drawable;
+        mAspectRatio = ((float) mDrawable.getIntrinsicWidth()) / intrinsicHeight;
+        setImageDrawable(drawable);
+        setContentDescription(message.getText());
+        return true;
+    }
+
+    static MessagingMessage createMessage(MessagingLayout layout,
+            Notification.MessagingStyle.Message m) {
+        MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
+        MessagingImageMessage createdMessage = sInstancePool.acquire();
+        if (createdMessage == null) {
+            createdMessage = (MessagingImageMessage) LayoutInflater.from(
+                    layout.getContext()).inflate(
+                            R.layout.notification_template_messaging_image_message,
+                            messagingLinearLayout,
+                            false);
+            createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+        }
+        boolean created = createdMessage.setMessage(m);
+        if (!created) {
+            createdMessage.recycle();
+            return MessagingTextMessage.createMessage(layout, m);
+        }
+        return createdMessage;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        canvas.save();
+        canvas.clipPath(getRoundedRectPath());
+        int width = (int) Math.max(getActualWidth(), getActualHeight() * mAspectRatio);
+        int height = (int) (width / mAspectRatio);
+        int left = (int) ((getActualWidth() - width) / 2.0f);
+        mDrawable.setBounds(left, 0, left + width, height);
+        mDrawable.draw(canvas);
+        canvas.restore();
+    }
+
+    public Path getRoundedRectPath() {
+        int left = 0;
+        int right = getActualWidth();
+        int top = 0;
+        int bottom = getActualHeight();
+        mPath.reset();
+        int width = right - left;
+        float roundnessX = mImageRounding;
+        float roundnessY = mImageRounding;
+        roundnessX = Math.min(width / 2, roundnessX);
+        roundnessY = Math.min((bottom - top) / 2, roundnessY);
+        mPath.moveTo(left, top + roundnessY);
+        mPath.quadTo(left, top, left + roundnessX, top);
+        mPath.lineTo(right - roundnessX, top);
+        mPath.quadTo(right, top, right, top + roundnessY);
+        mPath.lineTo(right, bottom - roundnessY);
+        mPath.quadTo(right, bottom, right - roundnessX, bottom);
+        mPath.lineTo(left + roundnessX, bottom);
+        mPath.quadTo(left, bottom, left, bottom - roundnessY);
+        mPath.close();
+        return mPath;
+    }
+
+    public void recycle() {
+        MessagingMessage.super.recycle();
+        setAlpha(1.0f);
+        setTranslationY(0);
+        setImageBitmap(null);
+        mDrawable = null;
+        sInstancePool.release(this);
+    }
+
+    public static void dropCache() {
+        sInstancePool = new Pools.SynchronizedPool<>(10);
+    }
+
+    @Override
+    public int getMeasuredType() {
+        int measuredHeight = getMeasuredHeight();
+        int minImageHeight;
+        if (mIsIsolated) {
+            minImageHeight = mIsolatedSize;
+        } else {
+            minImageHeight = mMinImageHeight;
+        }
+        boolean measuredTooSmall = measuredHeight < minImageHeight
+                && measuredHeight != mDrawable.getIntrinsicHeight();
+        if (measuredTooSmall) {
+            return MEASURED_TOO_SMALL;
+        } else {
+            if (!mIsIsolated && measuredHeight != mDrawable.getIntrinsicHeight()) {
+                return MEASURED_SHORTENED;
+            } else {
+                return MEASURED_NORMAL;
+            }
+        }
+    }
+
+    @Override
+    public void setMaxDisplayedLines(int lines) {
+        // Nothing to do, this should be handled automatically.
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (mIsIsolated) {
+            setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
+                    MeasureSpec.getSize(heightMeasureSpec));
+        }
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        // TODO: ensure that this isn't called when transforming
+        setActualWidth(getStaticWidth());
+        setActualHeight(getHeight());
+    }
+
+    @Override
+    public int getConsumedLines() {
+        return 3;
+    }
+
+    public void setActualWidth(int actualWidth) {
+        mActualWidth = actualWidth;
+        invalidate();
+    }
+
+    public int getActualWidth() {
+        return mActualWidth;
+    }
+
+    public void setActualHeight(int actualHeight) {
+        mActualHeight = actualHeight;
+        invalidate();
+    }
+
+    public int getActualHeight() {
+        return mActualHeight;
+    }
+
+    public int getStaticWidth() {
+        if (mIsIsolated) {
+            return getWidth();
+        }
+        return (int) (getHeight() * mAspectRatio);
+    }
+
+    public void setIsolated(boolean isolated) {
+        if (mIsIsolated != isolated) {
+            mIsIsolated = isolated;
+            // update the layout params not to have margins
+            ViewGroup.MarginLayoutParams layoutParams =
+                    (ViewGroup.MarginLayoutParams) getLayoutParams();
+            layoutParams.topMargin = isolated ? 0 : mExtraSpacing;
+            setLayoutParams(layoutParams);
+        }
+    }
+
+    @Override
+    public int getExtraSpacing() {
+        return mExtraSpacing;
+    }
+}
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index d45c086..5279636 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -81,6 +81,7 @@
     private ArrayList<MessagingGroup> mAddedGroups = new ArrayList<>();
     private Notification.Person mUser;
     private CharSequence mNameReplacement;
+    private boolean mIsCollapsed;
 
     public MessagingLayout(@NonNull Context context) {
         super(context);
@@ -127,6 +128,11 @@
     }
 
     @RemotableViewMethod
+    public void setIsCollapsed(boolean isCollapsed) {
+        mIsCollapsed = isCollapsed;
+    }
+
+    @RemotableViewMethod
     public void setData(Bundle extras) {
         Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
         List<Notification.MessagingStyle.Message> newMessages
@@ -331,6 +337,7 @@
                 newGroup = MessagingGroup.createGroup(mMessagingLinearLayout);
                 mAddedGroups.add(newGroup);
             }
+            newGroup.setDisplayAvatarsAtEnd(mIsCollapsed);
             newGroup.setLayoutColor(mLayoutColor);
             Notification.Person sender = senders.get(groupIndex);
             CharSequence nameOverride = null;
@@ -392,7 +399,6 @@
             MessagingMessage message = findAndRemoveMatchingMessage(m);
             if (message == null) {
                 message = MessagingMessage.createMessage(this, m);
-                message.addOnLayoutChangeListener(MESSAGING_PROPERTY_ANIMATOR);
             }
             message.setIsHistoric(historic);
             result.add(message);
diff --git a/core/java/com/android/internal/widget/MessagingLinearLayout.java b/core/java/com/android/internal/widget/MessagingLinearLayout.java
index f0ef370..991e3e7 100644
--- a/core/java/com/android/internal/widget/MessagingLinearLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLinearLayout.java
@@ -75,7 +75,6 @@
                 targetHeight = Integer.MAX_VALUE;
                 break;
         }
-        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
 
         // Now that we know which views to take, fix up the indents and see what width we get.
         int measuredWidth = mPaddingLeft + mPaddingRight;
@@ -90,7 +89,6 @@
         totalHeight = mPaddingTop + mPaddingBottom;
         boolean first = true;
         int linesRemaining = mMaxDisplayedLines;
-
         // Starting from the bottom: we measure every view as if it were the only one. If it still
         // fits, we take it, otherwise we stop there.
         for (int i = count - 1; i >= 0 && totalHeight < targetHeight; i--) {
@@ -100,11 +98,13 @@
             final View child = getChildAt(i);
             LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
             MessagingChild messagingChild = null;
+            int spacing = mSpacing;
             if (child instanceof MessagingChild) {
                 messagingChild = (MessagingChild) child;
                 messagingChild.setMaxDisplayedLines(linesRemaining);
+                spacing += messagingChild.getExtraSpacing();
             }
-            int spacing = first ? 0 : mSpacing;
+            spacing = first ? 0 : spacing;
             measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, totalHeight
                     - mPaddingTop - mPaddingBottom + spacing);
 
@@ -254,6 +254,9 @@
         void setMaxDisplayedLines(int lines);
         void hideAnimated();
         boolean isHidingAnimated();
+        default int getExtraSpacing() {
+            return 0;
+        }
     }
 
     public static class LayoutParams extends MarginLayoutParams {
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index f09621f..bf1c5ca 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -16,182 +16,125 @@
 
 package com.android.internal.widget;
 
-import android.annotation.AttrRes;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StyleRes;
 import android.app.Notification;
-import android.content.Context;
-import android.text.Layout;
-import android.util.AttributeSet;
-import android.util.Pools;
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.widget.RemoteViews;
-
-import com.android.internal.R;
+import android.view.View;
 
 import java.util.Objects;
 
 /**
  * A message of a {@link MessagingLayout}.
  */
-@RemoteViews.RemoteView
-public class MessagingMessage extends ImageFloatingTextView implements
-        MessagingLinearLayout.MessagingChild {
+public interface MessagingMessage extends MessagingLinearLayout.MessagingChild {
 
-    private static Pools.SimplePool<MessagingMessage> sInstancePool
-            = new Pools.SynchronizedPool<>(10);
-    private Notification.MessagingStyle.Message mMessage;
-    private MessagingGroup mGroup;
-    private boolean mIsHistoric;
-    private boolean mIsHidingAnimated;
+    /**
+     * Prefix for supported image MIME types
+     **/
+    String IMAGE_MIME_TYPE_PREFIX = "image/";
 
-    public MessagingMessage(@NonNull Context context) {
-        super(context);
+    static MessagingMessage createMessage(MessagingLayout layout,
+            Notification.MessagingStyle.Message m) {
+        if (hasImage(m)) {
+            return MessagingImageMessage.createMessage(layout, m);
+        } else {
+            return MessagingTextMessage.createMessage(layout, m);
+        }
     }
 
-    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
-        super(context, attrs);
+    static void dropCache() {
+        MessagingTextMessage.dropCache();
+        MessagingImageMessage.dropCache();
     }
 
-    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
-            @AttrRes int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
+    static boolean hasImage(Notification.MessagingStyle.Message m) {
+        return m.getDataUri() != null
+                && m.getDataMimeType() != null
+                && m.getDataMimeType().startsWith(IMAGE_MIME_TYPE_PREFIX);
     }
 
-    public MessagingMessage(@NonNull Context context, @Nullable AttributeSet attrs,
-            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
+    /**
+     * Set a message for this view.
+     * @return true if setting the message worked
+     */
+    default boolean setMessage(Notification.MessagingStyle.Message message) {
+        getState().setMessage(message);
+        return true;
     }
 
-    private void setMessage(Notification.MessagingStyle.Message message) {
-        mMessage = message;
-        setText(message.getText());
+    default Notification.MessagingStyle.Message getMessage() {
+        return getState().getMessage();
     }
 
-    public Notification.MessagingStyle.Message getMessage() {
-        return mMessage;
-    }
-
-    boolean sameAs(Notification.MessagingStyle.Message message) {
-        if (!Objects.equals(message.getText(), mMessage.getText())) {
+    default boolean sameAs(Notification.MessagingStyle.Message message) {
+        Notification.MessagingStyle.Message ownMessage = getMessage();
+        if (!Objects.equals(message.getText(), ownMessage.getText())) {
             return false;
         }
-        if (!Objects.equals(message.getSender(), mMessage.getSender())) {
+        if (!Objects.equals(message.getSender(), ownMessage.getSender())) {
             return false;
         }
-        if (!Objects.equals(message.getTimestamp(), mMessage.getTimestamp())) {
+        if (!Objects.equals(message.getTimestamp(), ownMessage.getTimestamp())) {
+            return false;
+        }
+        if (!Objects.equals(message.getDataMimeType(), ownMessage.getDataMimeType())) {
+            return false;
+        }
+        if (!Objects.equals(message.getDataUri(), ownMessage.getDataUri())) {
             return false;
         }
         return true;
     }
 
-    boolean sameAs(MessagingMessage message) {
+    default boolean sameAs(MessagingMessage message) {
         return sameAs(message.getMessage());
     }
 
-    static MessagingMessage createMessage(MessagingLayout layout,
-            Notification.MessagingStyle.Message m) {
-        MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
-        MessagingMessage createdMessage = sInstancePool.acquire();
-        if (createdMessage == null) {
-            createdMessage = (MessagingMessage) LayoutInflater.from(layout.getContext()).inflate(
-                    R.layout.notification_template_messaging_message, messagingLinearLayout,
-                    false);
-        }
-        createdMessage.setMessage(m);
-        return createdMessage;
+    default void removeMessage() {
+        getGroup().removeMessage(this);
     }
 
-    public void removeMessage() {
-        mGroup.removeMessage(this);
+    default void setMessagingGroup(MessagingGroup group) {
+        getState().setGroup(group);
     }
 
-    public void recycle() {
-        mGroup = null;
-        mMessage = null;
-        setAlpha(1.0f);
-        setTranslationY(0);
-        sInstancePool.release(this);
+    default void setIsHistoric(boolean isHistoric) {
+        getState().setIsHistoric(isHistoric);
     }
 
-    public void setMessagingGroup(MessagingGroup group) {
-        mGroup = group;
+    default MessagingGroup getGroup() {
+        return getState().getGroup();
     }
 
-    public static void dropCache() {
-        sInstancePool = new Pools.SynchronizedPool<>(10);
-    }
-
-    public void setIsHistoric(boolean isHistoric) {
-        mIsHistoric = isHistoric;
-    }
-
-    public MessagingGroup getGroup() {
-        return mGroup;
+    default void setIsHidingAnimated(boolean isHiding) {
+        getState().setIsHidingAnimated(isHiding);
     }
 
     @Override
-    public int getMeasuredType() {
-        boolean measuredTooSmall = getMeasuredHeight()
-                < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
-        if (measuredTooSmall) {
-            return MEASURED_TOO_SMALL;
-        } else {
-            Layout layout = getLayout();
-            if (layout == null) {
-                return MEASURED_TOO_SMALL;
-            }
-            if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
-                return MEASURED_SHORTENED;
-            } else {
-                return MEASURED_NORMAL;
-            }
-        }
+    default boolean isHidingAnimated() {
+        return getState().isHidingAnimated();
     }
 
     @Override
-    public void hideAnimated() {
+    default void hideAnimated() {
         setIsHidingAnimated(true);
-        mGroup.performRemoveAnimation(this, () -> setIsHidingAnimated(false));
+        getGroup().performRemoveAnimation(getState().getHostView(),
+                () -> setIsHidingAnimated(false));
     }
 
-    private void setIsHidingAnimated(boolean isHiding) {
-        ViewParent parent = getParent();
-        mIsHidingAnimated = isHiding;
-        invalidate();
-        if (parent instanceof ViewGroup) {
-            ((ViewGroup) parent).invalidate();
-        }
-    }
-
-    @Override
-    public boolean isHidingAnimated() {
-        return mIsHidingAnimated;
-    }
-
-    @Override
-    public void setMaxDisplayedLines(int lines) {
-        setMaxLines(lines);
-    }
-
-    @Override
-    public int getConsumedLines() {
-        return getLineCount();
-    }
-
-    public int getLayoutHeight() {
-        Layout layout = getLayout();
-        if (layout == null) {
-            return 0;
-        }
-        return layout.getHeight();
-    }
-
-    @Override
-    public boolean hasOverlappingRendering() {
+    default boolean hasOverlappingRendering() {
         return false;
     }
+
+    default void recycle() {
+        getState().reset();
+    }
+
+    default View getView() {
+        return (View) this;
+    }
+
+    default void setColor(int textColor) {}
+
+    MessagingMessageState getState();
+
+    void setVisibility(int visibility);
 }
diff --git a/core/java/com/android/internal/widget/MessagingMessageState.java b/core/java/com/android/internal/widget/MessagingMessageState.java
new file mode 100644
index 0000000..ac62472
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingMessageState.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.app.Notification;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+/**
+ * Shared state and implementation for MessagingMessages. Used to share common implementations.
+ */
+public class MessagingMessageState {
+    private final View mHostView;
+    private Notification.MessagingStyle.Message mMessage;
+    private MessagingGroup mGroup;
+    private boolean mIsHistoric;
+    private boolean mIsHidingAnimated;
+
+    MessagingMessageState(View hostView) {
+        mHostView = hostView;
+    }
+
+    public void setMessage(Notification.MessagingStyle.Message message) {
+        mMessage = message;
+    }
+
+    public Notification.MessagingStyle.Message getMessage() {
+        return mMessage;
+    }
+
+    public void setGroup(MessagingGroup group) {
+        mGroup = group;
+    }
+
+    public MessagingGroup getGroup() {
+        return mGroup;
+    }
+
+    public void setIsHistoric(boolean isHistoric) {
+        mIsHistoric = isHistoric;
+    }
+
+    public void setIsHidingAnimated(boolean isHiding) {
+        ViewParent parent = mHostView.getParent();
+        mIsHidingAnimated = isHiding;
+        mHostView.invalidate();
+        if (parent instanceof ViewGroup) {
+            ((ViewGroup) parent).invalidate();
+        }
+    }
+
+    public boolean isHidingAnimated() {
+        return mIsHidingAnimated;
+    }
+
+    public View getHostView() {
+        return mHostView;
+    }
+
+    public void reset() {
+        mIsHidingAnimated = false;
+        mIsHistoric = false;
+        mGroup = null;
+        mMessage = null;
+    }
+}
diff --git a/core/java/com/android/internal/widget/MessagingTextMessage.java b/core/java/com/android/internal/widget/MessagingTextMessage.java
new file mode 100644
index 0000000..794cc1d
--- /dev/null
+++ b/core/java/com/android/internal/widget/MessagingTextMessage.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.AttrRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StyleRes;
+import android.app.Notification;
+import android.content.Context;
+import android.text.Layout;
+import android.util.AttributeSet;
+import android.util.Pools;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.RemoteViews;
+
+import com.android.internal.R;
+
+import java.util.Objects;
+
+/**
+ * A message of a {@link MessagingLayout}.
+ */
+@RemoteViews.RemoteView
+public class MessagingTextMessage extends ImageFloatingTextView implements MessagingMessage {
+
+    private static Pools.SimplePool<MessagingTextMessage> sInstancePool
+            = new Pools.SynchronizedPool<>(20);
+    private final MessagingMessageState mState = new MessagingMessageState(this);
+
+    public MessagingTextMessage(@NonNull Context context) {
+        super(context);
+    }
+
+    public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public MessagingTextMessage(@NonNull Context context, @Nullable AttributeSet attrs,
+            @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    @Override
+    public MessagingMessageState getState() {
+        return mState;
+    }
+
+    @Override
+    public boolean setMessage(Notification.MessagingStyle.Message message) {
+        MessagingMessage.super.setMessage(message);
+        setText(message.getText());
+        return true;
+    }
+
+    static MessagingMessage createMessage(MessagingLayout layout,
+            Notification.MessagingStyle.Message m) {
+        MessagingLinearLayout messagingLinearLayout = layout.getMessagingLinearLayout();
+        MessagingTextMessage createdMessage = sInstancePool.acquire();
+        if (createdMessage == null) {
+            createdMessage = (MessagingTextMessage) LayoutInflater.from(
+                    layout.getContext()).inflate(
+                            R.layout.notification_template_messaging_text_message,
+                            messagingLinearLayout,
+                            false);
+            createdMessage.addOnLayoutChangeListener(MessagingLayout.MESSAGING_PROPERTY_ANIMATOR);
+        }
+        createdMessage.setMessage(m);
+        return createdMessage;
+    }
+
+    public void recycle() {
+        MessagingMessage.super.recycle();
+        setAlpha(1.0f);
+        setTranslationY(0);
+        sInstancePool.release(this);
+    }
+
+    public static void dropCache() {
+        sInstancePool = new Pools.SynchronizedPool<>(10);
+    }
+
+    @Override
+    public int getMeasuredType() {
+        boolean measuredTooSmall = getMeasuredHeight()
+                < getLayoutHeight() + getPaddingTop() + getPaddingBottom();
+        if (measuredTooSmall) {
+            return MEASURED_TOO_SMALL;
+        } else {
+            Layout layout = getLayout();
+            if (layout == null) {
+                return MEASURED_TOO_SMALL;
+            }
+            if (layout.getEllipsisCount(layout.getLineCount() - 1) > 0) {
+                return MEASURED_SHORTENED;
+            } else {
+                return MEASURED_NORMAL;
+            }
+        }
+    }
+
+    @Override
+    public void setMaxDisplayedLines(int lines) {
+        setMaxLines(lines);
+    }
+
+    @Override
+    public int getConsumedLines() {
+        return getLineCount();
+    }
+
+    public int getLayoutHeight() {
+        Layout layout = getLayout();
+        if (layout == null) {
+            return 0;
+        }
+        return layout.getHeight();
+    }
+
+    @Override
+    public void setColor(int color) {
+        setTextColor(color);
+    }
+}
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 33f80ce..78a3e13 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -110,8 +110,8 @@
         "android_util_AssetManager.cpp",
         "android_util_Binder.cpp",
         "android_util_EventLog.cpp",
-        "android_util_MemoryIntArray.cpp",
         "android_util_Log.cpp",
+        "android_util_MemoryIntArray.cpp",
         "android_util_PathParser.cpp",
         "android_util_Process.cpp",
         "android_util_StringBlock.cpp",
@@ -191,6 +191,7 @@
         "android_backup_FileBackupHelperBase.cpp",
         "android_backup_BackupHelperDispatcher.cpp",
         "android_app_backup_FullBackup.cpp",
+        "android_content_res_ApkAssets.cpp",
         "android_content_res_ObbScanner.cpp",
         "android_content_res_Configuration.cpp",
         "android_animation_PropertyValuesHolder.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index d202173..4a032c4 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -123,6 +123,7 @@
 extern int register_android_util_PathParser(JNIEnv* env);
 extern int register_android_content_StringBlock(JNIEnv* env);
 extern int register_android_content_XmlBlock(JNIEnv* env);
+extern int register_android_content_res_ApkAssets(JNIEnv* env);
 extern int register_android_graphics_Canvas(JNIEnv* env);
 extern int register_android_graphics_CanvasProperty(JNIEnv* env);
 extern int register_android_graphics_ColorFilter(JNIEnv* env);
@@ -1346,6 +1347,7 @@
     REG_JNI(register_android_content_AssetManager),
     REG_JNI(register_android_content_StringBlock),
     REG_JNI(register_android_content_XmlBlock),
+    REG_JNI(register_android_content_res_ApkAssets),
     REG_JNI(register_android_text_AndroidCharacter),
     REG_JNI(register_android_text_Hyphenator),
     REG_JNI(register_android_text_MeasuredParagraph),
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index 937b3ff..ed032c7 100644
--- a/core/jni/android/graphics/FontFamily.cpp
+++ b/core/jni/android/graphics/FontFamily.cpp
@@ -28,7 +28,7 @@
 #include <nativehelper/ScopedUtfChars.h>
 #include <android_runtime/AndroidRuntime.h>
 #include <android_runtime/android_util_AssetManager.h>
-#include <androidfw/AssetManager.h>
+#include <androidfw/AssetManager2.h>
 #include "Utils.h"
 #include "FontUtils.h"
 
@@ -90,7 +90,7 @@
 }
 
 static bool addSkTypeface(NativeFamilyBuilder* builder, sk_sp<SkData>&& data, int ttcIndex,
-        jint givenWeight, jint givenItalic) {
+        jint weight, jint italic) {
     uirenderer::FatVector<SkFontArguments::Axis, 2> skiaAxes;
     for (const auto& axis : builder->axes) {
         skiaAxes.emplace_back(SkFontArguments::Axis{axis.axisTag, axis.value});
@@ -114,27 +114,15 @@
     std::shared_ptr<minikin::MinikinFont> minikinFont =
             std::make_shared<MinikinFontSkia>(std::move(face), fontPtr, fontSize, ttcIndex,
                     builder->axes);
+    minikin::Font::Builder fontBuilder(minikinFont);
 
-    int weight = givenWeight;
-    bool italic = givenItalic == 1;
-    if (givenWeight == RESOLVE_BY_FONT_TABLE || givenItalic == RESOLVE_BY_FONT_TABLE) {
-        int os2Weight;
-        bool os2Italic;
-        if (!minikin::FontFamily::analyzeStyle(minikinFont, &os2Weight, &os2Italic)) {
-            ALOGE("analyzeStyle failed. Using default style");
-            os2Weight = 400;
-            os2Italic = false;
-        }
-        if (givenWeight == RESOLVE_BY_FONT_TABLE) {
-            weight = os2Weight;
-        }
-        if (givenItalic == RESOLVE_BY_FONT_TABLE) {
-            italic = os2Italic;
-        }
+    if (weight != RESOLVE_BY_FONT_TABLE) {
+        fontBuilder.setWeight(weight);
     }
-
-    builder->fonts.push_back(minikin::Font(minikinFont,
-            minikin::FontStyle(weight, static_cast<minikin::FontStyle::Slant>(italic))));
+    if (italic != RESOLVE_BY_FONT_TABLE) {
+        fontBuilder.setSlant(static_cast<minikin::FontStyle::Slant>(italic != 0));
+    }
+    builder->fonts.push_back(fontBuilder.build());
     builder->axes.clear();
     return true;
 }
@@ -217,7 +205,8 @@
     NPE_CHECK_RETURN_ZERO(env, jpath);
 
     NativeFamilyBuilder* builder = reinterpret_cast<NativeFamilyBuilder*>(builderPtr);
-    AssetManager* mgr = assetManagerForJavaObject(env, jassetMgr);
+
+    Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(env, jassetMgr);
     if (NULL == mgr) {
         builder->axes.clear();
         return false;
@@ -229,27 +218,33 @@
         return false;
     }
 
-    Asset* asset;
-    if (isAsset) {
-        asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
-    } else {
-        asset = cookie ? mgr->openNonAsset(static_cast<int32_t>(cookie), str.c_str(),
-                Asset::ACCESS_BUFFER) : mgr->openNonAsset(str.c_str(), Asset::ACCESS_BUFFER);
+    std::unique_ptr<Asset> asset;
+    {
+      ScopedLock<AssetManager2> locked_mgr(*mgr);
+      if (isAsset) {
+          asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER);
+      } else if (cookie > 0) {
+          // Valid java cookies are 1-based, but AssetManager cookies are 0-based.
+          asset = locked_mgr->OpenNonAsset(str.c_str(), static_cast<ApkAssetsCookie>(cookie - 1),
+                  Asset::ACCESS_BUFFER);
+      } else {
+          asset = locked_mgr->OpenNonAsset(str.c_str(), Asset::ACCESS_BUFFER);
+      }
     }
 
-    if (NULL == asset) {
+    if (nullptr == asset) {
         builder->axes.clear();
         return false;
     }
 
     const void* buf = asset->getBuffer(false);
     if (NULL == buf) {
-        delete asset;
         builder->axes.clear();
         return false;
     }
 
-    sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset, asset));
+    sk_sp<SkData> data(SkData::MakeWithProc(buf, asset->getLength(), releaseAsset,
+            asset.release()));
     return addSkTypeface(builder, std::move(data), ttcIndex, weight, isItalic);
 }
 
diff --git a/core/jni/android/graphics/Paint.cpp b/core/jni/android/graphics/Paint.cpp
index 115d0d5..482d028 100644
--- a/core/jni/android/graphics/Paint.cpp
+++ b/core/jni/android/graphics/Paint.cpp
@@ -576,7 +576,7 @@
         minikin::FakedFont baseFont = typeface->fFontCollection->baseFontFaked(typeface->fStyle);
         float saveSkewX = paint->getTextSkewX();
         bool savefakeBold = paint->isFakeBoldText();
-        MinikinFontSkia::populateSkPaint(paint, baseFont.font, baseFont.fakery);
+        MinikinFontSkia::populateSkPaint(paint, baseFont.font->typeface().get(), baseFont.fakery);
         SkScalar spacing = paint->getFontMetrics(metrics);
         // The populateSkPaint call may have changed fake bold / text skew
         // because we want to measure with those effects applied, so now
diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp
index 09e37e1..49a24a3 100644
--- a/core/jni/android_app_NativeActivity.cpp
+++ b/core/jni/android_app_NativeActivity.cpp
@@ -361,7 +361,7 @@
     code->sdkVersion = sdkVersion;
 
     code->javaAssetManager = env->NewGlobalRef(jAssetMgr);
-    code->assetManager = assetManagerForJavaObject(env, jAssetMgr);
+    code->assetManager = NdkAssetManagerForJavaObject(env, jAssetMgr);
 
     if (obbDir != NULL) {
         dirStr = env->GetStringUTFChars(obbDir, NULL);
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
new file mode 100644
index 0000000..c0f151b
--- /dev/null
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+#include "android-base/unique_fd.h"
+#include "androidfw/ApkAssets.h"
+#include "utils/misc.h"
+
+#include "core_jni_helpers.h"
+#include "jni.h"
+#include "nativehelper/ScopedUtfChars.h"
+
+using ::android::base::unique_fd;
+
+namespace android {
+
+static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
+                        jboolean force_shared_lib, jboolean overlay) {
+  ScopedUtfChars path(env, java_path);
+  if (path.c_str() == nullptr) {
+    return 0;
+  }
+
+  std::unique_ptr<const ApkAssets> apk_assets;
+  if (overlay) {
+    apk_assets = ApkAssets::LoadOverlay(path.c_str(), system);
+  } else if (force_shared_lib) {
+    apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system);
+  } else {
+    apk_assets = ApkAssets::Load(path.c_str(), system);
+  }
+
+  if (apk_assets == nullptr) {
+    std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
+    jniThrowException(env, "java/io/IOException", error_msg.c_str());
+    return 0;
+  }
+  return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
+                              jstring friendly_name, jboolean system, jboolean force_shared_lib) {
+  ScopedUtfChars friendly_name_utf8(env, friendly_name);
+  if (friendly_name_utf8.c_str() == nullptr) {
+    return 0;
+  }
+
+  int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
+  if (fd < 0) {
+    jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+    return 0;
+  }
+
+  unique_fd dup_fd(::dup(fd));
+  if (dup_fd < 0) {
+    jniThrowIOException(env, errno);
+    return 0;
+  }
+
+  std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd),
+                                                                      friendly_name_utf8.c_str(),
+                                                                      system, force_shared_lib);
+  if (apk_assets == nullptr) {
+    std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d",
+                                               friendly_name_utf8.c_str(), dup_fd.get());
+    jniThrowException(env, "java/io/IOException", error_msg.c_str());
+    return 0;
+  }
+  return reinterpret_cast<jlong>(apk_assets.release());
+}
+
+static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+  delete reinterpret_cast<ApkAssets*>(ptr);
+}
+
+static jstring NativeGetAssetPath(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+  const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+  return env->NewStringUTF(apk_assets->GetPath().c_str());
+}
+
+static jlong NativeGetStringBlock(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+  const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+  return reinterpret_cast<jlong>(apk_assets->GetLoadedArsc()->GetStringPool());
+}
+
+static jboolean NativeIsUpToDate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+  const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+  (void)apk_assets;
+  return JNI_TRUE;
+}
+
+static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring file_name) {
+  ScopedUtfChars path_utf8(env, file_name);
+  if (path_utf8.c_str() == nullptr) {
+    return 0;
+  }
+
+  const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
+  std::unique_ptr<Asset> asset = apk_assets->Open(path_utf8.c_str(),
+                                                  Asset::AccessMode::ACCESS_RANDOM);
+  if (asset == nullptr) {
+    jniThrowException(env, "java/io/FileNotFoundException", path_utf8.c_str());
+    return 0;
+  }
+
+  // DynamicRefTable is only needed when looking up resource references. Opening an XML file
+  // directly from an ApkAssets has no notion of proper resource references.
+  std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(nullptr /*dynamicRefTable*/);
+  status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
+  asset.reset();
+
+  if (err != NO_ERROR) {
+    jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+    return 0;
+  }
+  return reinterpret_cast<jlong>(xml_tree.release());
+}
+
+// JNI registration.
+static const JNINativeMethod gApkAssetsMethods[] = {
+    {"nativeLoad", "(Ljava/lang/String;ZZZ)J", (void*)NativeLoad},
+    {"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZ)J",
+        (void*)NativeLoadFromFd},
+    {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+    {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
+    {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
+    {"nativeIsUpToDate", "(J)Z", (void*)NativeIsUpToDate},
+    {"nativeOpenXml", "(JLjava/lang/String;)J", (void*)NativeOpenXml},
+};
+
+int register_android_content_res_ApkAssets(JNIEnv* env) {
+  return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods,
+                              arraysize(gApkAssetsMethods));
+}
+
+}  // namespace android
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 683b4c4..c623ca6 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1,1851 +1,1441 @@
-/* //device/libs/android_runtime/android_util_AssetManager.cpp
-**
-** Copyright 2006, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
-*/
+/*
+ * Copyright 2006, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 #define LOG_TAG "asset"
 
-#include <android_runtime/android_util_AssetManager.h>
-
 #include <inttypes.h>
 #include <linux/capability.h>
 #include <stdio.h>
-#include <sys/types.h>
-#include <sys/wait.h>
 #include <sys/stat.h>
 #include <sys/system_properties.h>
+#include <sys/types.h>
+#include <sys/wait.h>
 
 #include <private/android_filesystem_config.h> // for AID_SYSTEM
 
-#include "androidfw/Asset.h"
-#include "androidfw/AssetManager.h"
-#include "androidfw/AttributeResolution.h"
-#include "androidfw/ResourceTypes.h"
+#include "android-base/logging.h"
+#include "android-base/properties.h"
+#include "android-base/stringprintf.h"
+#include "android_runtime/android_util_AssetManager.h"
 #include "android_runtime/AndroidRuntime.h"
 #include "android_util_Binder.h"
+#include "androidfw/Asset.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/AttributeResolution.h"
+#include "androidfw/MutexGuard.h"
+#include "androidfw/ResourceTypes.h"
 #include "core_jni_helpers.h"
 #include "jni.h"
-#include <nativehelper/JNIHelp.h>
-#include <nativehelper/ScopedStringChars.h>
-#include <nativehelper/ScopedUtfChars.h>
+#include "nativehelper/JNIHelp.h"
+#include "nativehelper/ScopedPrimitiveArray.h"
+#include "nativehelper/ScopedStringChars.h"
+#include "nativehelper/ScopedUtfChars.h"
 #include "utils/Log.h"
-#include "utils/misc.h"
 #include "utils/String8.h"
+#include "utils/misc.h"
 
 extern "C" int capget(cap_user_header_t hdrp, cap_user_data_t datap);
 extern "C" int capset(cap_user_header_t hdrp, const cap_user_data_t datap);
 
+using ::android::base::StringPrintf;
 
 namespace android {
 
-static const bool kThrowOnBadId = false;
-
 // ----------------------------------------------------------------------------
 
-static struct typedvalue_offsets_t
-{
-    jfieldID mType;
-    jfieldID mData;
-    jfieldID mString;
-    jfieldID mAssetCookie;
-    jfieldID mResourceId;
-    jfieldID mChangingConfigurations;
-    jfieldID mDensity;
+static struct typedvalue_offsets_t {
+  jfieldID mType;
+  jfieldID mData;
+  jfieldID mString;
+  jfieldID mAssetCookie;
+  jfieldID mResourceId;
+  jfieldID mChangingConfigurations;
+  jfieldID mDensity;
 } gTypedValueOffsets;
 
-static struct assetfiledescriptor_offsets_t
-{
-    jfieldID mFd;
-    jfieldID mStartOffset;
-    jfieldID mLength;
+static struct assetfiledescriptor_offsets_t {
+  jfieldID mFd;
+  jfieldID mStartOffset;
+  jfieldID mLength;
 } gAssetFileDescriptorOffsets;
 
-static struct assetmanager_offsets_t
-{
-    jfieldID mObject;
+static struct assetmanager_offsets_t {
+  jfieldID mObject;
 } gAssetManagerOffsets;
 
-static struct sparsearray_offsets_t
-{
-    jclass classObject;
-    jmethodID constructor;
-    jmethodID put;
+static struct {
+  jfieldID native_ptr;
+} gApkAssetsFields;
+
+static struct sparsearray_offsets_t {
+  jclass classObject;
+  jmethodID constructor;
+  jmethodID put;
 } gSparseArrayOffsets;
 
-static struct configuration_offsets_t
-{
-    jclass classObject;
-    jmethodID constructor;
-    jfieldID mSmallestScreenWidthDpOffset;
-    jfieldID mScreenWidthDpOffset;
-    jfieldID mScreenHeightDpOffset;
+static struct configuration_offsets_t {
+  jclass classObject;
+  jmethodID constructor;
+  jfieldID mSmallestScreenWidthDpOffset;
+  jfieldID mScreenWidthDpOffset;
+  jfieldID mScreenHeightDpOffset;
 } gConfigurationOffsets;
 
-jclass g_stringClass = NULL;
+jclass g_stringClass = nullptr;
 
 // ----------------------------------------------------------------------------
 
-static jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
-                      const Res_value& value, uint32_t ref, ssize_t block,
-                      uint32_t typeSpecFlags, ResTable_config* config = NULL);
+// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0.
+constexpr inline static jint ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) {
+  return cookie != kInvalidCookie ? static_cast<jint>(cookie + 1) : -1;
+}
 
-jint copyValue(JNIEnv* env, jobject outValue, const ResTable* table,
-               const Res_value& value, uint32_t ref, ssize_t block,
-               uint32_t typeSpecFlags, ResTable_config* config)
-{
-    env->SetIntField(outValue, gTypedValueOffsets.mType, value.dataType);
-    env->SetIntField(outValue, gTypedValueOffsets.mAssetCookie,
-                     static_cast<jint>(table->getTableCookie(block)));
-    env->SetIntField(outValue, gTypedValueOffsets.mData, value.data);
-    env->SetObjectField(outValue, gTypedValueOffsets.mString, NULL);
-    env->SetIntField(outValue, gTypedValueOffsets.mResourceId, ref);
-    env->SetIntField(outValue, gTypedValueOffsets.mChangingConfigurations,
-            typeSpecFlags);
-    if (config != NULL) {
-        env->SetIntField(outValue, gTypedValueOffsets.mDensity, config->density);
-    }
-    return block;
+constexpr inline static ApkAssetsCookie JavaCookieToApkAssetsCookie(jint cookie) {
+  return cookie > 0 ? static_cast<ApkAssetsCookie>(cookie - 1) : kInvalidCookie;
 }
 
 // This is called by zygote (running as user root) as part of preloadResources.
-static void verifySystemIdmaps()
-{
-    pid_t pid;
-    char system_id[10];
+static void NativeVerifySystemIdmaps(JNIEnv* /*env*/, jclass /*clazz*/) {
+  switch (pid_t pid = fork()) {
+    case -1:
+      PLOG(ERROR) << "failed to fork for idmap";
+      break;
 
-    snprintf(system_id, sizeof(system_id), "%d", AID_SYSTEM);
+    // child
+    case 0: {
+      struct __user_cap_header_struct capheader;
+      struct __user_cap_data_struct capdata;
 
-    switch (pid = fork()) {
-        case -1:
-            ALOGE("failed to fork for idmap: %s", strerror(errno));
-            break;
-        case 0: // child
-            {
-                struct __user_cap_header_struct capheader;
-                struct __user_cap_data_struct capdata;
+      memset(&capheader, 0, sizeof(capheader));
+      memset(&capdata, 0, sizeof(capdata));
 
-                memset(&capheader, 0, sizeof(capheader));
-                memset(&capdata, 0, sizeof(capdata));
+      capheader.version = _LINUX_CAPABILITY_VERSION;
+      capheader.pid = 0;
 
-                capheader.version = _LINUX_CAPABILITY_VERSION;
-                capheader.pid = 0;
+      if (capget(&capheader, &capdata) != 0) {
+        PLOG(ERROR) << "capget";
+        exit(1);
+      }
 
-                if (capget(&capheader, &capdata) != 0) {
-                    ALOGE("capget: %s\n", strerror(errno));
-                    exit(1);
-                }
+      capdata.effective = capdata.permitted;
+      if (capset(&capheader, &capdata) != 0) {
+        PLOG(ERROR) << "capset";
+        exit(1);
+      }
 
-                capdata.effective = capdata.permitted;
-                if (capset(&capheader, &capdata) != 0) {
-                    ALOGE("capset: %s\n", strerror(errno));
-                    exit(1);
-                }
+      if (setgid(AID_SYSTEM) != 0) {
+        PLOG(ERROR) << "setgid";
+        exit(1);
+      }
 
-                if (setgid(AID_SYSTEM) != 0) {
-                    ALOGE("setgid: %s\n", strerror(errno));
-                    exit(1);
-                }
+      if (setuid(AID_SYSTEM) != 0) {
+        PLOG(ERROR) << "setuid";
+        exit(1);
+      }
 
-                if (setuid(AID_SYSTEM) != 0) {
-                    ALOGE("setuid: %s\n", strerror(errno));
-                    exit(1);
-                }
+      // Generic idmap parameters
+      const char* argv[8];
+      int argc = 0;
+      struct stat st;
 
-                // Generic idmap parameters
-                const char* argv[8];
-                int argc = 0;
-                struct stat st;
+      memset(argv, 0, sizeof(argv));
+      argv[argc++] = AssetManager::IDMAP_BIN;
+      argv[argc++] = "--scan";
+      argv[argc++] = AssetManager::TARGET_PACKAGE_NAME;
+      argv[argc++] = AssetManager::TARGET_APK_PATH;
+      argv[argc++] = AssetManager::IDMAP_DIR;
 
-                memset(argv, NULL, sizeof(argv));
-                argv[argc++] = AssetManager::IDMAP_BIN;
-                argv[argc++] = "--scan";
-                argv[argc++] = AssetManager::TARGET_PACKAGE_NAME;
-                argv[argc++] = AssetManager::TARGET_APK_PATH;
-                argv[argc++] = AssetManager::IDMAP_DIR;
-
-                // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
-                // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
-                char subdir[PROP_VALUE_MAX];
-                int len = __system_property_get(AssetManager::OVERLAY_THEME_DIR_PROPERTY, subdir);
-                if (len > 0) {
-                    String8 overlayPath = String8(AssetManager::OVERLAY_DIR) + "/" + subdir;
-                    if (stat(overlayPath.string(), &st) == 0) {
-                        argv[argc++] = overlayPath.string();
-                    }
-                }
-                if (stat(AssetManager::OVERLAY_DIR, &st) == 0) {
-                    argv[argc++] = AssetManager::OVERLAY_DIR;
-                }
-
-                if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) {
-                    argv[argc++] = AssetManager::PRODUCT_OVERLAY_DIR;
-                }
-
-                // Finally, invoke idmap (if any overlay directory exists)
-                if (argc > 5) {
-                    execv(AssetManager::IDMAP_BIN, (char* const*)argv);
-                    ALOGE("failed to execv for idmap: %s", strerror(errno));
-                    exit(1); // should never get here
-                } else {
-                    exit(0);
-                }
-            }
-            break;
-        default: // parent
-            waitpid(pid, NULL, 0);
-            break;
-    }
-}
-
-
-// ----------------------------------------------------------------------------
-
-// this guy is exported to other jni routines
-AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject obj)
-{
-    jlong amHandle = env->GetLongField(obj, gAssetManagerOffsets.mObject);
-    AssetManager* am = reinterpret_cast<AssetManager*>(amHandle);
-    if (am != NULL) {
-        return am;
-    }
-    jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!");
-    return NULL;
-}
-
-static jlong android_content_AssetManager_openAsset(JNIEnv* env, jobject clazz,
-                                                jstring fileName, jint mode)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-
-    ALOGV("openAsset in %p (Java object %p)\n", am, clazz);
-
-    ScopedUtfChars fileName8(env, fileName);
-    if (fileName8.c_str() == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", "Empty file name");
-        return -1;
-    }
-
-    if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM
-        && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
-        return -1;
-    }
-
-    Asset* a = am->open(fileName8.c_str(), (Asset::AccessMode)mode);
-
-    if (a == NULL) {
-        jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
-        return -1;
-    }
-
-    //printf("Created Asset Stream: %p\n", a);
-
-    return reinterpret_cast<jlong>(a);
-}
-
-static jobject returnParcelFileDescriptor(JNIEnv* env, Asset* a, jlongArray outOffsets)
-{
-    off64_t startOffset, length;
-    int fd = a->openFileDescriptor(&startOffset, &length);
-    delete a;
-
-    if (fd < 0) {
-        jniThrowException(env, "java/io/FileNotFoundException",
-                "This file can not be opened as a file descriptor; it is probably compressed");
-        return NULL;
-    }
-
-    jlong* offsets = (jlong*)env->GetPrimitiveArrayCritical(outOffsets, 0);
-    if (offsets == NULL) {
-        close(fd);
-        return NULL;
-    }
-
-    offsets[0] = startOffset;
-    offsets[1] = length;
-
-    env->ReleasePrimitiveArrayCritical(outOffsets, offsets, 0);
-
-    jobject fileDesc = jniCreateFileDescriptor(env, fd);
-    if (fileDesc == NULL) {
-        close(fd);
-        return NULL;
-    }
-
-    return newParcelFileDescriptor(env, fileDesc);
-}
-
-static jobject android_content_AssetManager_openAssetFd(JNIEnv* env, jobject clazz,
-                                                jstring fileName, jlongArray outOffsets)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-
-    ALOGV("openAssetFd in %p (Java object %p)\n", am, clazz);
-
-    ScopedUtfChars fileName8(env, fileName);
-    if (fileName8.c_str() == NULL) {
-        return NULL;
-    }
-
-    Asset* a = am->open(fileName8.c_str(), Asset::ACCESS_RANDOM);
-
-    if (a == NULL) {
-        jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
-        return NULL;
-    }
-
-    //printf("Created Asset Stream: %p\n", a);
-
-    return returnParcelFileDescriptor(env, a, outOffsets);
-}
-
-static jlong android_content_AssetManager_openNonAssetNative(JNIEnv* env, jobject clazz,
-                                                         jint cookie,
-                                                         jstring fileName,
-                                                         jint mode)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-
-    ALOGV("openNonAssetNative in %p (Java object %p)\n", am, clazz);
-
-    ScopedUtfChars fileName8(env, fileName);
-    if (fileName8.c_str() == NULL) {
-        return -1;
-    }
-
-    if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM
-        && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
-        return -1;
-    }
-
-    Asset* a = cookie
-        ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(),
-                (Asset::AccessMode)mode)
-        : am->openNonAsset(fileName8.c_str(), (Asset::AccessMode)mode);
-
-    if (a == NULL) {
-        jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
-        return -1;
-    }
-
-    //printf("Created Asset Stream: %p\n", a);
-
-    return reinterpret_cast<jlong>(a);
-}
-
-static jobject android_content_AssetManager_openNonAssetFdNative(JNIEnv* env, jobject clazz,
-                                                         jint cookie,
-                                                         jstring fileName,
-                                                         jlongArray outOffsets)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-
-    ALOGV("openNonAssetFd in %p (Java object %p)\n", am, clazz);
-
-    ScopedUtfChars fileName8(env, fileName);
-    if (fileName8.c_str() == NULL) {
-        return NULL;
-    }
-
-    Asset* a = cookie
-        ? am->openNonAsset(static_cast<int32_t>(cookie), fileName8.c_str(), Asset::ACCESS_RANDOM)
-        : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_RANDOM);
-
-    if (a == NULL) {
-        jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
-        return NULL;
-    }
-
-    //printf("Created Asset Stream: %p\n", a);
-
-    return returnParcelFileDescriptor(env, a, outOffsets);
-}
-
-static jobjectArray android_content_AssetManager_list(JNIEnv* env, jobject clazz,
-                                                   jstring fileName)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-
-    ScopedUtfChars fileName8(env, fileName);
-    if (fileName8.c_str() == NULL) {
-        return NULL;
-    }
-
-    AssetDir* dir = am->openDir(fileName8.c_str());
-
-    if (dir == NULL) {
-        jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
-        return NULL;
-    }
-
-    size_t N = dir->getFileCount();
-
-    jobjectArray array = env->NewObjectArray(dir->getFileCount(),
-                                                g_stringClass, NULL);
-    if (array == NULL) {
-        delete dir;
-        return NULL;
-    }
-
-    for (size_t i=0; i<N; i++) {
-        const String8& name = dir->getFileName(i);
-        jstring str = env->NewStringUTF(name.string());
-        if (str == NULL) {
-            delete dir;
-            return NULL;
+      // Directories to scan for overlays: if OVERLAY_THEME_DIR_PROPERTY is defined,
+      // use OVERLAY_DIR/<value of OVERLAY_THEME_DIR_PROPERTY> in addition to OVERLAY_DIR.
+      std::string overlay_theme_path = base::GetProperty(AssetManager::OVERLAY_THEME_DIR_PROPERTY,
+                                                         "");
+      if (!overlay_theme_path.empty()) {
+        overlay_theme_path = std::string(AssetManager::OVERLAY_DIR) + "/" + overlay_theme_path;
+        if (stat(overlay_theme_path.c_str(), &st) == 0) {
+          argv[argc++] = overlay_theme_path.c_str();
         }
-        env->SetObjectArrayElement(array, i, str);
-        env->DeleteLocalRef(str);
-    }
+      }
 
-    delete dir;
+      if (stat(AssetManager::OVERLAY_DIR, &st) == 0) {
+        argv[argc++] = AssetManager::OVERLAY_DIR;
+      }
 
-    return array;
+      if (stat(AssetManager::PRODUCT_OVERLAY_DIR, &st) == 0) {
+        argv[argc++] = AssetManager::PRODUCT_OVERLAY_DIR;
+      }
+
+      // Finally, invoke idmap (if any overlay directory exists)
+      if (argc > 5) {
+        execv(AssetManager::IDMAP_BIN, (char* const*)argv);
+        PLOG(ERROR) << "failed to execv for idmap";
+        exit(1); // should never get here
+      } else {
+        exit(0);
+      }
+  } break;
+
+  // parent
+  default:
+    waitpid(pid, nullptr, 0);
+    break;
+  }
 }
 
-static void android_content_AssetManager_destroyAsset(JNIEnv* env, jobject clazz,
-                                                      jlong assetHandle)
-{
-    Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
-    //printf("Destroying Asset Stream: %p\n", a);
-
-    if (a == NULL) {
-        jniThrowNullPointerException(env, "asset");
-        return;
-    }
-
-    delete a;
-}
-
-static jint android_content_AssetManager_readAssetChar(JNIEnv* env, jobject clazz,
-                                                       jlong assetHandle)
-{
-    Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
-    if (a == NULL) {
-        jniThrowNullPointerException(env, "asset");
-        return -1;
-    }
-
-    uint8_t b;
-    ssize_t res = a->read(&b, 1);
-    return res == 1 ? b : -1;
-}
-
-static jint android_content_AssetManager_readAsset(JNIEnv* env, jobject clazz,
-                                                jlong assetHandle, jbyteArray bArray,
-                                                jint off, jint len)
-{
-    Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
-    if (a == NULL || bArray == NULL) {
-        jniThrowNullPointerException(env, "asset");
-        return -1;
-    }
-
-    if (len == 0) {
-        return 0;
-    }
-
-    jsize bLen = env->GetArrayLength(bArray);
-    if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "");
-        return -1;
-    }
-
-    jbyte* b = env->GetByteArrayElements(bArray, NULL);
-    ssize_t res = a->read(b+off, len);
-    env->ReleaseByteArrayElements(bArray, b, 0);
-
-    if (res > 0) return static_cast<jint>(res);
-
-    if (res < 0) {
-        jniThrowException(env, "java/io/IOException", "");
-    }
-    return -1;
-}
-
-static jlong android_content_AssetManager_seekAsset(JNIEnv* env, jobject clazz,
-                                                 jlong assetHandle,
-                                                 jlong offset, jint whence)
-{
-    Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
-    if (a == NULL) {
-        jniThrowNullPointerException(env, "asset");
-        return -1;
-    }
-
-    return a->seek(
-        offset, (whence > 0) ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR));
-}
-
-static jlong android_content_AssetManager_getAssetLength(JNIEnv* env, jobject clazz,
-                                                      jlong assetHandle)
-{
-    Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
-    if (a == NULL) {
-        jniThrowNullPointerException(env, "asset");
-        return -1;
-    }
-
-    return a->getLength();
-}
-
-static jlong android_content_AssetManager_getAssetRemainingLength(JNIEnv* env, jobject clazz,
-                                                               jlong assetHandle)
-{
-    Asset* a = reinterpret_cast<Asset*>(assetHandle);
-
-    if (a == NULL) {
-        jniThrowNullPointerException(env, "asset");
-        return -1;
-    }
-
-    return a->getRemainingLength();
-}
-
-static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
-                                                       jstring path, jboolean appAsLib)
-{
-    ScopedUtfChars path8(env, path);
-    if (path8.c_str() == NULL) {
-        return 0;
-    }
-
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-
-    int32_t cookie;
-    bool res = am->addAssetPath(String8(path8.c_str()), &cookie, appAsLib);
-
-    return (res) ? static_cast<jint>(cookie) : 0;
-}
-
-static jint android_content_AssetManager_addOverlayPath(JNIEnv* env, jobject clazz,
-                                                     jstring idmapPath)
-{
-    ScopedUtfChars idmapPath8(env, idmapPath);
-    if (idmapPath8.c_str() == NULL) {
-        return 0;
-    }
-
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-
-    int32_t cookie;
-    bool res = am->addOverlayPath(String8(idmapPath8.c_str()), &cookie);
-
-    return (res) ? (jint)cookie : 0;
-}
-
-static jint android_content_AssetManager_addAssetFd(JNIEnv* env, jobject clazz,
-                                                    jobject fileDescriptor, jstring debugPathName,
-                                                    jboolean appAsLib)
-{
-    ScopedUtfChars debugPathName8(env, debugPathName);
-
-    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
-    if (fd < 0) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
-        return 0;
-    }
-
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-
-    int dupfd = ::dup(fd);
-    if (dupfd < 0) {
-        jniThrowIOException(env, errno);
-        return 0;
-    }
-
-    int32_t cookie;
-    bool res = am->addAssetFd(dupfd, String8(debugPathName8.c_str()), &cookie, appAsLib);
-
-    return (res) ? static_cast<jint>(cookie) : 0;
-}
-
-static jboolean android_content_AssetManager_isUpToDate(JNIEnv* env, jobject clazz)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return JNI_TRUE;
-    }
-    return am->isUpToDate() ? JNI_TRUE : JNI_FALSE;
-}
-
-static jobjectArray getLocales(JNIEnv* env, jobject clazz, bool includeSystemLocales)
-{
-    Vector<String8> locales;
-
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-
-    am->getLocales(&locales, includeSystemLocales);
-
-    const int N = locales.size();
-
-    jobjectArray result = env->NewObjectArray(N, g_stringClass, NULL);
-    if (result == NULL) {
-        return NULL;
-    }
-
-    for (int i=0; i<N; i++) {
-        jstring str = env->NewStringUTF(locales[i].string());
-        if (str == NULL) {
-            return NULL;
-        }
-        env->SetObjectArrayElement(result, i, str);
-        env->DeleteLocalRef(str);
-    }
-
-    return result;
-}
-
-static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz)
-{
-    return getLocales(env, clazz, true /* include system locales */);
-}
-
-static jobjectArray android_content_AssetManager_getNonSystemLocales(JNIEnv* env, jobject clazz)
-{
-    return getLocales(env, clazz, false /* don't include system locales */);
-}
-
-static jobject constructConfigurationObject(JNIEnv* env, const ResTable_config& config) {
-    jobject result = env->NewObject(gConfigurationOffsets.classObject,
-            gConfigurationOffsets.constructor);
-    if (result == NULL) {
-        return NULL;
-    }
-
-    env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset,
-            config.smallestScreenWidthDp);
-    env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp);
-    env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp);
-
-    return result;
-}
-
-static jobjectArray getSizeConfigurationsInternal(JNIEnv* env,
-        const Vector<ResTable_config>& configs) {
-    const int N = configs.size();
-    jobjectArray result = env->NewObjectArray(N, gConfigurationOffsets.classObject, NULL);
-    if (result == NULL) {
-        return NULL;
-    }
-
-    for (int i=0; i<N; i++) {
-        jobject config = constructConfigurationObject(env, configs[i]);
-        if (config == NULL) {
-            env->DeleteLocalRef(result);
-            return NULL;
-        }
-
-        env->SetObjectArrayElement(result, i, config);
-        env->DeleteLocalRef(config);
-    }
-
-    return result;
-}
-
-static jobjectArray android_content_AssetManager_getSizeConfigurations(JNIEnv* env, jobject clazz) {
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-
-    const ResTable& res(am->getResources());
-    Vector<ResTable_config> configs;
-    res.getConfigurations(&configs, false /* ignoreMipmap */, true /* ignoreAndroidPackage */);
-
-    return getSizeConfigurationsInternal(env, configs);
-}
-
-static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject clazz,
-                                                          jint mcc, jint mnc,
-                                                          jstring locale, jint orientation,
-                                                          jint touchscreen, jint density,
-                                                          jint keyboard, jint keyboardHidden,
-                                                          jint navigation,
-                                                          jint screenWidth, jint screenHeight,
-                                                          jint smallestScreenWidthDp,
-                                                          jint screenWidthDp, jint screenHeightDp,
-                                                          jint screenLayout, jint uiMode,
-                                                          jint colorMode, jint sdkVersion)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return;
-    }
-
-    ResTable_config config;
-    memset(&config, 0, sizeof(config));
-
-    const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL;
-
-    // Constants duplicated from Java class android.content.res.Configuration.
-    static const jint kScreenLayoutRoundMask = 0x300;
-    static const jint kScreenLayoutRoundShift = 8;
-
-    config.mcc = (uint16_t)mcc;
-    config.mnc = (uint16_t)mnc;
-    config.orientation = (uint8_t)orientation;
-    config.touchscreen = (uint8_t)touchscreen;
-    config.density = (uint16_t)density;
-    config.keyboard = (uint8_t)keyboard;
-    config.inputFlags = (uint8_t)keyboardHidden;
-    config.navigation = (uint8_t)navigation;
-    config.screenWidth = (uint16_t)screenWidth;
-    config.screenHeight = (uint16_t)screenHeight;
-    config.smallestScreenWidthDp = (uint16_t)smallestScreenWidthDp;
-    config.screenWidthDp = (uint16_t)screenWidthDp;
-    config.screenHeightDp = (uint16_t)screenHeightDp;
-    config.screenLayout = (uint8_t)screenLayout;
-    config.uiMode = (uint8_t)uiMode;
-    config.colorMode = (uint8_t)colorMode;
-    config.sdkVersion = (uint16_t)sdkVersion;
-    config.minorVersion = 0;
-
-    // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
-    // in C++. We must extract the round qualifier out of the Java screenLayout and put it
-    // into screenLayout2.
-    config.screenLayout2 =
-            (uint8_t)((screenLayout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
-
-    am->setConfiguration(config, locale8);
-
-    if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8);
-}
-
-static jint android_content_AssetManager_getResourceIdentifier(JNIEnv* env, jobject clazz,
-                                                            jstring name,
-                                                            jstring defType,
-                                                            jstring defPackage)
-{
-    ScopedStringChars name16(env, name);
-    if (name16.get() == NULL) {
-        return 0;
-    }
-
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-
-    const char16_t* defType16 = reinterpret_cast<const char16_t*>(defType)
-        ? reinterpret_cast<const char16_t*>(env->GetStringChars(defType, NULL))
-        : NULL;
-    jsize defTypeLen = defType
-        ? env->GetStringLength(defType) : 0;
-    const char16_t* defPackage16 = reinterpret_cast<const char16_t*>(defPackage)
-        ? reinterpret_cast<const char16_t*>(env->GetStringChars(defPackage,
-                                                                NULL))
-        : NULL;
-    jsize defPackageLen = defPackage
-        ? env->GetStringLength(defPackage) : 0;
-
-    jint ident = am->getResources().identifierForName(
-        reinterpret_cast<const char16_t*>(name16.get()), name16.size(),
-        defType16, defTypeLen, defPackage16, defPackageLen);
-
-    if (defPackage16) {
-        env->ReleaseStringChars(defPackage,
-                                reinterpret_cast<const jchar*>(defPackage16));
-    }
-    if (defType16) {
-        env->ReleaseStringChars(defType,
-                                reinterpret_cast<const jchar*>(defType16));
-    }
-
-    return ident;
-}
-
-static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject clazz,
-                                                            jint resid)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-
-    ResTable::resource_name name;
-    if (!am->getResources().getResourceName(resid, true, &name)) {
-        return NULL;
-    }
-
-    String16 str;
-    if (name.package != NULL) {
-        str.setTo(name.package, name.packageLen);
-    }
-    if (name.type8 != NULL || name.type != NULL) {
-        if (str.size() > 0) {
-            char16_t div = ':';
-            str.append(&div, 1);
-        }
-        if (name.type8 != NULL) {
-            str.append(String16(name.type8, name.typeLen));
-        } else {
-            str.append(name.type, name.typeLen);
-        }
-    }
-    if (name.name8 != NULL || name.name != NULL) {
-        if (str.size() > 0) {
-            char16_t div = '/';
-            str.append(&div, 1);
-        }
-        if (name.name8 != NULL) {
-            str.append(String16(name.name8, name.nameLen));
-        } else {
-            str.append(name.name, name.nameLen);
-        }
-    }
-
-    return env->NewString((const jchar*)str.string(), str.size());
-}
-
-static jstring android_content_AssetManager_getResourcePackageName(JNIEnv* env, jobject clazz,
-                                                                   jint resid)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-
-    ResTable::resource_name name;
-    if (!am->getResources().getResourceName(resid, true, &name)) {
-        return NULL;
-    }
-
-    if (name.package != NULL) {
-        return env->NewString((const jchar*)name.package, name.packageLen);
-    }
-
-    return NULL;
-}
-
-static jstring android_content_AssetManager_getResourceTypeName(JNIEnv* env, jobject clazz,
-                                                                jint resid)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-
-    ResTable::resource_name name;
-    if (!am->getResources().getResourceName(resid, true, &name)) {
-        return NULL;
-    }
-
-    if (name.type8 != NULL) {
-        return env->NewStringUTF(name.type8);
-    }
-
-    if (name.type != NULL) {
-        return env->NewString((const jchar*)name.type, name.typeLen);
-    }
-
-    return NULL;
-}
-
-static jstring android_content_AssetManager_getResourceEntryName(JNIEnv* env, jobject clazz,
-                                                                 jint resid)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-
-    ResTable::resource_name name;
-    if (!am->getResources().getResourceName(resid, true, &name)) {
-        return NULL;
-    }
-
-    if (name.name8 != NULL) {
-        return env->NewStringUTF(name.name8);
-    }
-
-    if (name.name != NULL) {
-        return env->NewString((const jchar*)name.name, name.nameLen);
-    }
-
-    return NULL;
-}
-
-static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject clazz,
-                                                           jint ident,
-                                                           jshort density,
-                                                           jobject outValue,
-                                                           jboolean resolve)
-{
-    if (outValue == NULL) {
-         jniThrowNullPointerException(env, "outValue");
-         return 0;
-    }
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-    const ResTable& res(am->getResources());
-
-    Res_value value;
-    ResTable_config config;
-    uint32_t typeSpecFlags;
-    ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
-    if (kThrowOnBadId) {
-        if (block == BAD_INDEX) {
-            jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
-            return 0;
-        }
-    }
-    uint32_t ref = ident;
-    if (resolve) {
-        block = res.resolveReference(&value, block, &ref, &typeSpecFlags, &config);
-        if (kThrowOnBadId) {
-            if (block == BAD_INDEX) {
-                jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
-                return 0;
-            }
-        }
-    }
-    if (block >= 0) {
-        return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags, &config);
-    }
-
-    return static_cast<jint>(block);
-}
-
-static jint android_content_AssetManager_loadResourceBagValue(JNIEnv* env, jobject clazz,
-                                                           jint ident, jint bagEntryId,
-                                                           jobject outValue, jboolean resolve)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-    const ResTable& res(am->getResources());
-
-    // Now lock down the resource object and start pulling stuff from it.
-    res.lock();
-
-    ssize_t block = -1;
-    Res_value value;
-
-    const ResTable::bag_entry* entry = NULL;
-    uint32_t typeSpecFlags;
-    ssize_t entryCount = res.getBagLocked(ident, &entry, &typeSpecFlags);
-
-    for (ssize_t i=0; i<entryCount; i++) {
-        if (((uint32_t)bagEntryId) == entry->map.name.ident) {
-            block = entry->stringBlock;
-            value = entry->map.value;
-        }
-        entry++;
-    }
-
-    res.unlock();
-
-    if (block < 0) {
-        return static_cast<jint>(block);
-    }
-
-    uint32_t ref = ident;
-    if (resolve) {
-        block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
-        if (kThrowOnBadId) {
-            if (block == BAD_INDEX) {
-                jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
-                return 0;
-            }
-        }
-    }
-    if (block >= 0) {
-        return copyValue(env, outValue, &res, value, ref, block, typeSpecFlags);
-    }
-
-    return static_cast<jint>(block);
-}
-
-static jint android_content_AssetManager_getStringBlockCount(JNIEnv* env, jobject clazz)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-    return am->getResources().getTableCount();
-}
-
-static jlong android_content_AssetManager_getNativeStringBlock(JNIEnv* env, jobject clazz,
-                                                           jint block)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-    return reinterpret_cast<jlong>(am->getResources().getTableStringBlock(block));
-}
-
-static jstring android_content_AssetManager_getCookieName(JNIEnv* env, jobject clazz,
-                                                       jint cookie)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-    String8 name(am->getAssetPath(static_cast<int32_t>(cookie)));
-    if (name.length() == 0) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "Empty cookie name");
-        return NULL;
-    }
-    jstring str = env->NewStringUTF(name.string());
-    return str;
-}
-
-static jobject android_content_AssetManager_getAssignedPackageIdentifiers(JNIEnv* env, jobject clazz)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-
-    const ResTable& res = am->getResources();
-
-    jobject sparseArray = env->NewObject(gSparseArrayOffsets.classObject,
-            gSparseArrayOffsets.constructor);
-    const size_t N = res.getBasePackageCount();
-    for (size_t i = 0; i < N; i++) {
-        const String16 name = res.getBasePackageName(i);
-        env->CallVoidMethod(
-            sparseArray, gSparseArrayOffsets.put,
-            static_cast<jint>(res.getBasePackageId(i)),
-            env->NewString(reinterpret_cast<const jchar*>(name.string()),
-                           name.size()));
-    }
-    return sparseArray;
-}
-
-static jlong android_content_AssetManager_newTheme(JNIEnv* env, jobject clazz)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-    return reinterpret_cast<jlong>(new ResTable::Theme(am->getResources()));
-}
-
-static void android_content_AssetManager_deleteTheme(JNIEnv* env, jobject clazz,
-                                                     jlong themeHandle)
-{
-    ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
-    delete theme;
-}
-
-static void android_content_AssetManager_applyThemeStyle(JNIEnv* env, jobject clazz,
-                                                         jlong themeHandle,
-                                                         jint styleRes,
-                                                         jboolean force)
-{
-    ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
-    theme->applyStyle(styleRes, force ? true : false);
-}
-
-static void android_content_AssetManager_copyTheme(JNIEnv* env, jobject clazz,
-                                                   jlong destHandle, jlong srcHandle)
-{
-    ResTable::Theme* dest = reinterpret_cast<ResTable::Theme*>(destHandle);
-    ResTable::Theme* src = reinterpret_cast<ResTable::Theme*>(srcHandle);
-    dest->setTo(*src);
-}
-
-static void android_content_AssetManager_clearTheme(JNIEnv* env, jobject clazz, jlong themeHandle)
-{
-    ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
-    theme->clear();
-}
-
-static jint android_content_AssetManager_loadThemeAttributeValue(
-    JNIEnv* env, jobject clazz, jlong themeHandle, jint ident, jobject outValue, jboolean resolve)
-{
-    ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
-    const ResTable& res(theme->getResTable());
-
-    Res_value value;
-    // XXX value could be different in different configs!
-    uint32_t typeSpecFlags = 0;
-    ssize_t block = theme->getAttribute(ident, &value, &typeSpecFlags);
-    uint32_t ref = 0;
-    if (resolve) {
-        block = res.resolveReference(&value, block, &ref, &typeSpecFlags);
-        if (kThrowOnBadId) {
-            if (block == BAD_INDEX) {
-                jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
-                return 0;
-            }
-        }
-    }
-    return block >= 0 ? copyValue(env, outValue, &res, value, ref, block, typeSpecFlags) : block;
-}
-
-static jint android_content_AssetManager_getThemeChangingConfigurations(JNIEnv* env, jobject clazz,
-                                                                        jlong themeHandle)
-{
-    ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
-    return theme->getChangingConfigurations();
-}
-
-static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz,
-                                                   jlong themeHandle, jint pri,
-                                                   jstring tag, jstring prefix)
-{
-    ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeHandle);
-    const ResTable& res(theme->getResTable());
-    (void)res;
-
-    // XXX Need to use params.
-    theme->dumpToLog();
-}
-
-static jboolean android_content_AssetManager_resolveAttrs(JNIEnv* env, jobject clazz,
-                                                          jlong themeToken,
-                                                          jint defStyleAttr,
-                                                          jint defStyleRes,
-                                                          jintArray inValues,
-                                                          jintArray attrs,
-                                                          jintArray outValues,
-                                                          jintArray outIndices)
-{
-    if (themeToken == 0) {
-        jniThrowNullPointerException(env, "theme token");
-        return JNI_FALSE;
-    }
-    if (attrs == NULL) {
-        jniThrowNullPointerException(env, "attrs");
-        return JNI_FALSE;
-    }
-    if (outValues == NULL) {
-        jniThrowNullPointerException(env, "out values");
-        return JNI_FALSE;
-    }
-
-    const jsize NI = env->GetArrayLength(attrs);
-    const jsize NV = env->GetArrayLength(outValues);
-    if (NV < (NI*STYLE_NUM_ENTRIES)) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small");
-        return JNI_FALSE;
-    }
-
-    jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
-    if (src == NULL) {
-        return JNI_FALSE;
-    }
-
-    jint* srcValues = (jint*)env->GetPrimitiveArrayCritical(inValues, 0);
-    const jsize NSV = srcValues == NULL ? 0 : env->GetArrayLength(inValues);
-
-    jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
-    if (baseDest == NULL) {
-        env->ReleasePrimitiveArrayCritical(attrs, src, 0);
-        return JNI_FALSE;
-    }
-
-    jint* indices = NULL;
-    if (outIndices != NULL) {
-        if (env->GetArrayLength(outIndices) > NI) {
-            indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0);
-        }
-    }
-
-    ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
-    bool result = ResolveAttrs(theme, defStyleAttr, defStyleRes,
-                               (uint32_t*) srcValues, NSV,
-                               (uint32_t*) src, NI,
-                               (uint32_t*) baseDest,
-                               (uint32_t*) indices);
-
-    if (indices != NULL) {
-        env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
-    }
-    env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
-    env->ReleasePrimitiveArrayCritical(inValues, srcValues, 0);
-    env->ReleasePrimitiveArrayCritical(attrs, src, 0);
-    return result ? JNI_TRUE : JNI_FALSE;
-}
-
-static void android_content_AssetManager_applyStyle(JNIEnv* env, jobject, jlong themeToken,
-        jint defStyleAttr, jint defStyleRes, jlong xmlParserToken, jintArray attrsObj, jint length,
-        jlong outValuesAddress, jlong outIndicesAddress) {
-    jint* attrs = env->GetIntArrayElements(attrsObj, 0);
-    ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken);
-    ResXMLParser* xmlParser = reinterpret_cast<ResXMLParser*>(xmlParserToken);
-    uint32_t* outValues = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outValuesAddress));
-    uint32_t* outIndices = reinterpret_cast<uint32_t*>(static_cast<uintptr_t>(outIndicesAddress));
-    ApplyStyle(theme, xmlParser, defStyleAttr, defStyleRes,
-            reinterpret_cast<const uint32_t*>(attrs), length, outValues, outIndices);
-    env->ReleaseIntArrayElements(attrsObj, attrs, JNI_ABORT);
-}
-
-static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, jobject clazz,
-                                                        jlong xmlParserToken,
-                                                        jintArray attrs,
-                                                        jintArray outValues,
-                                                        jintArray outIndices)
-{
-    if (xmlParserToken == 0) {
-        jniThrowNullPointerException(env, "xmlParserToken");
-        return JNI_FALSE;
-    }
-    if (attrs == NULL) {
-        jniThrowNullPointerException(env, "attrs");
-        return JNI_FALSE;
-    }
-    if (outValues == NULL) {
-        jniThrowNullPointerException(env, "out values");
-        return JNI_FALSE;
-    }
-
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return JNI_FALSE;
-    }
-    const ResTable& res(am->getResources());
-    ResXMLParser* xmlParser = (ResXMLParser*)xmlParserToken;
-
-    const jsize NI = env->GetArrayLength(attrs);
-    const jsize NV = env->GetArrayLength(outValues);
-    if (NV < (NI*STYLE_NUM_ENTRIES)) {
-        jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small");
-        return JNI_FALSE;
-    }
-
-    jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0);
-    if (src == NULL) {
-        return JNI_FALSE;
-    }
-
-    jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
-    if (baseDest == NULL) {
-        env->ReleasePrimitiveArrayCritical(attrs, src, 0);
-        return JNI_FALSE;
-    }
-
-    jint* indices = NULL;
-    if (outIndices != NULL) {
-        if (env->GetArrayLength(outIndices) > NI) {
-            indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0);
-        }
-    }
-
-    bool result = RetrieveAttributes(&res, xmlParser,
-                                     (uint32_t*) src, NI,
-                                     (uint32_t*) baseDest,
-                                     (uint32_t*) indices);
-
-    if (indices != NULL) {
-        env->ReleasePrimitiveArrayCritical(outIndices, indices, 0);
-    }
-    env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
-    env->ReleasePrimitiveArrayCritical(attrs, src, 0);
-    return result ? JNI_TRUE : JNI_FALSE;
-}
-
-static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz,
-                                                       jint id)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-    const ResTable& res(am->getResources());
-
-    res.lock();
-    const ResTable::bag_entry* defStyleEnt = NULL;
-    ssize_t bagOff = res.getBagLocked(id, &defStyleEnt);
-    res.unlock();
-
-    return static_cast<jint>(bagOff);
-}
-
-static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject clazz,
-                                                        jint id,
-                                                        jintArray outValues)
-{
-    if (outValues == NULL) {
-        jniThrowNullPointerException(env, "out values");
-        return JNI_FALSE;
-    }
-
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return JNI_FALSE;
-    }
-    const ResTable& res(am->getResources());
-    ResTable_config config;
-    Res_value value;
-    ssize_t block;
-
-    const jsize NV = env->GetArrayLength(outValues);
-
-    jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0);
-    jint* dest = baseDest;
-    if (dest == NULL) {
-        jniThrowException(env, "java/lang/OutOfMemoryError", "");
-        return JNI_FALSE;
-    }
-
-    // Now lock down the resource object and start pulling stuff from it.
-    res.lock();
-
-    const ResTable::bag_entry* arrayEnt = NULL;
-    uint32_t arrayTypeSetFlags = 0;
-    ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags);
-    const ResTable::bag_entry* endArrayEnt = arrayEnt +
-        (bagOff >= 0 ? bagOff : 0);
-
-    int i = 0;
-    uint32_t typeSetFlags;
-    while (i < NV && arrayEnt < endArrayEnt) {
-        block = arrayEnt->stringBlock;
-        typeSetFlags = arrayTypeSetFlags;
-        config.density = 0;
-        value = arrayEnt->map.value;
-
-        uint32_t resid = 0;
-        if (value.dataType != Res_value::TYPE_NULL) {
-            // Take care of resolving the found resource to its final value.
-            //printf("Resolving attribute reference\n");
-            ssize_t newBlock = res.resolveReference(&value, block, &resid,
-                    &typeSetFlags, &config);
-            if (kThrowOnBadId) {
-                if (newBlock == BAD_INDEX) {
-                    jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
-                    return JNI_FALSE;
-                }
-            }
-            if (newBlock >= 0) block = newBlock;
-        }
-
-        // Deal with the special @null value -- it turns back to TYPE_NULL.
-        if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
-            value.dataType = Res_value::TYPE_NULL;
-            value.data = Res_value::DATA_NULL_UNDEFINED;
-        }
-
-        //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
-
-        // Write the final value back to Java.
-        dest[STYLE_TYPE] = value.dataType;
-        dest[STYLE_DATA] = value.data;
-        dest[STYLE_ASSET_COOKIE] = reinterpret_cast<jint>(res.getTableCookie(block));
-        dest[STYLE_RESOURCE_ID] = resid;
-        dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags;
-        dest[STYLE_DENSITY] = config.density;
-        dest += STYLE_NUM_ENTRIES;
-        i+= STYLE_NUM_ENTRIES;
-        arrayEnt++;
-    }
-
-    i /= STYLE_NUM_ENTRIES;
-
-    res.unlock();
-
-    env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0);
-
-    return i;
-}
-
-static jlong android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject clazz,
-                                                         jint cookie,
-                                                         jstring fileName)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return 0;
-    }
-
-    ALOGV("openXmlAsset in %p (Java object %p)\n", am, clazz);
-
-    ScopedUtfChars fileName8(env, fileName);
-    if (fileName8.c_str() == NULL) {
-        return 0;
-    }
-
-    int32_t assetCookie = static_cast<int32_t>(cookie);
-    Asset* a = assetCookie
-        ? am->openNonAsset(assetCookie, fileName8.c_str(), Asset::ACCESS_BUFFER)
-        : am->openNonAsset(fileName8.c_str(), Asset::ACCESS_BUFFER, &assetCookie);
-
-    if (a == NULL) {
-        jniThrowException(env, "java/io/FileNotFoundException", fileName8.c_str());
-        return 0;
-    }
-
-    const DynamicRefTable* dynamicRefTable =
-            am->getResources().getDynamicRefTableForCookie(assetCookie);
-    ResXMLTree* block = new ResXMLTree(dynamicRefTable);
-    status_t err = block->setTo(a->getBuffer(true), a->getLength(), true);
-    a->close();
-    delete a;
-
-    if (err != NO_ERROR) {
-        jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
-        return 0;
-    }
-
-    return reinterpret_cast<jlong>(block);
-}
-
-static jintArray android_content_AssetManager_getArrayStringInfo(JNIEnv* env, jobject clazz,
-                                                                 jint arrayResId)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-    const ResTable& res(am->getResources());
-
-    const ResTable::bag_entry* startOfBag;
-    const ssize_t N = res.lockBag(arrayResId, &startOfBag);
-    if (N < 0) {
-        return NULL;
-    }
-
-    jintArray array = env->NewIntArray(N * 2);
-    if (array == NULL) {
-        res.unlockBag(startOfBag);
-        return NULL;
-    }
-
-    Res_value value;
-    const ResTable::bag_entry* bag = startOfBag;
-    for (size_t i = 0, j = 0; ((ssize_t)i)<N; i++, bag++) {
-        jint stringIndex = -1;
-        jint stringBlock = 0;
-        value = bag->map.value;
-
-        // Take care of resolving the found resource to its final value.
-        stringBlock = res.resolveReference(&value, bag->stringBlock, NULL);
-        if (value.dataType == Res_value::TYPE_STRING) {
-            stringIndex = value.data;
-        }
-
-        if (kThrowOnBadId) {
-            if (stringBlock == BAD_INDEX) {
-                jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
-                return array;
-            }
-        }
-
-        //todo: It might be faster to allocate a C array to contain
-        //      the blocknums and indices, put them in there and then
-        //      do just one SetIntArrayRegion()
-        env->SetIntArrayRegion(array, j, 1, &stringBlock);
-        env->SetIntArrayRegion(array, j + 1, 1, &stringIndex);
-        j = j + 2;
-    }
-    res.unlockBag(startOfBag);
-    return array;
-}
-
-static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* env, jobject clazz,
-                                                                        jint arrayResId)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-    const ResTable& res(am->getResources());
-
-    const ResTable::bag_entry* startOfBag;
-    const ssize_t N = res.lockBag(arrayResId, &startOfBag);
-    if (N < 0) {
-        return NULL;
-    }
-
-    jobjectArray array = env->NewObjectArray(N, g_stringClass, NULL);
-    if (env->ExceptionCheck()) {
-        res.unlockBag(startOfBag);
-        return NULL;
-    }
-
-    Res_value value;
-    const ResTable::bag_entry* bag = startOfBag;
-    size_t strLen = 0;
-    for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
-        value = bag->map.value;
-        jstring str = NULL;
-
-        // Take care of resolving the found resource to its final value.
-        ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
-        if (kThrowOnBadId) {
-            if (block == BAD_INDEX) {
-                jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
-                return array;
-            }
-        }
-        if (value.dataType == Res_value::TYPE_STRING) {
-            const ResStringPool* pool = res.getTableStringBlock(block);
-            const char* str8 = pool->string8At(value.data, &strLen);
-            if (str8 != NULL) {
-                str = env->NewStringUTF(str8);
-            } else {
-                const char16_t* str16 = pool->stringAt(value.data, &strLen);
-                str = env->NewString(reinterpret_cast<const jchar*>(str16),
-                                     strLen);
-            }
-
-            // If one of our NewString{UTF} calls failed due to memory, an
-            // exception will be pending.
-            if (env->ExceptionCheck()) {
-                res.unlockBag(startOfBag);
-                return NULL;
-            }
-
-            env->SetObjectArrayElement(array, i, str);
-
-            // str is not NULL at that point, otherwise ExceptionCheck would have been true.
-            // If we have a large amount of strings in our array, we might
-            // overflow the local reference table of the VM.
-            env->DeleteLocalRef(str);
-        }
-    }
-    res.unlockBag(startOfBag);
-    return array;
-}
-
-static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, jobject clazz,
-                                                                        jint arrayResId)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-    const ResTable& res(am->getResources());
-
-    const ResTable::bag_entry* startOfBag;
-    const ssize_t N = res.lockBag(arrayResId, &startOfBag);
-    if (N < 0) {
-        return NULL;
-    }
-
-    jintArray array = env->NewIntArray(N);
-    if (array == NULL) {
-        res.unlockBag(startOfBag);
-        return NULL;
-    }
-
-    Res_value value;
-    const ResTable::bag_entry* bag = startOfBag;
-    for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
-        value = bag->map.value;
-
-        // Take care of resolving the found resource to its final value.
-        ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL);
-        if (kThrowOnBadId) {
-            if (block == BAD_INDEX) {
-                jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
-                return array;
-            }
-        }
-        if (value.dataType >= Res_value::TYPE_FIRST_INT
-                && value.dataType <= Res_value::TYPE_LAST_INT) {
-            int intVal = value.data;
-            env->SetIntArrayRegion(array, i, 1, &intVal);
-        }
-    }
-    res.unlockBag(startOfBag);
-    return array;
-}
-
-static jintArray android_content_AssetManager_getStyleAttributes(JNIEnv* env, jobject clazz,
-                                                                 jint styleId)
-{
-    AssetManager* am = assetManagerForJavaObject(env, clazz);
-    if (am == NULL) {
-        return NULL;
-    }
-    const ResTable& res(am->getResources());
-
-    const ResTable::bag_entry* startOfBag;
-    const ssize_t N = res.lockBag(styleId, &startOfBag);
-    if (N < 0) {
-        return NULL;
-    }
-
-    jintArray array = env->NewIntArray(N);
-    if (array == NULL) {
-        res.unlockBag(startOfBag);
-        return NULL;
-    }
-
-    const ResTable::bag_entry* bag = startOfBag;
-    for (size_t i=0; ((ssize_t)i)<N; i++, bag++) {
-        int resourceId = bag->map.name.ident;
-        env->SetIntArrayRegion(array, i, 1, &resourceId);
-    }
-    res.unlockBag(startOfBag);
-    return array;
-}
-
-static void android_content_AssetManager_init(JNIEnv* env, jobject clazz, jboolean isSystem)
-{
-    if (isSystem) {
-        verifySystemIdmaps();
-    }
-    AssetManager* am = new AssetManager();
-    if (am == NULL) {
-        jniThrowException(env, "java/lang/OutOfMemoryError", "");
-        return;
-    }
-
-    am->addDefaultAssets();
-
-    ALOGV("Created AssetManager %p for Java object %p\n", am, clazz);
-    env->SetLongField(clazz, gAssetManagerOffsets.mObject, reinterpret_cast<jlong>(am));
-}
-
-static void android_content_AssetManager_destroy(JNIEnv* env, jobject clazz)
-{
-    AssetManager* am = (AssetManager*)
-        (env->GetLongField(clazz, gAssetManagerOffsets.mObject));
-    ALOGV("Destroying AssetManager %p for Java object %p\n", am, clazz);
-    if (am != NULL) {
-        delete am;
-        env->SetLongField(clazz, gAssetManagerOffsets.mObject, 0);
-    }
-}
-
-static jint android_content_AssetManager_getGlobalAssetCount(JNIEnv* env, jobject clazz)
-{
-    return Asset::getGlobalCount();
-}
-
-static jobject android_content_AssetManager_getAssetAllocations(JNIEnv* env, jobject clazz)
-{
-    String8 alloc = Asset::getAssetAllocations();
-    if (alloc.length() <= 0) {
-        return NULL;
-    }
-
-    jstring str = env->NewStringUTF(alloc.string());
-    return str;
-}
-
-static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env, jobject clazz)
-{
-    return AssetManager::getGlobalCount();
+static jint CopyValue(JNIEnv* env, ApkAssetsCookie cookie, const Res_value& value, uint32_t ref,
+                      uint32_t type_spec_flags, ResTable_config* config, jobject out_typed_value) {
+  env->SetIntField(out_typed_value, gTypedValueOffsets.mType, value.dataType);
+  env->SetIntField(out_typed_value, gTypedValueOffsets.mAssetCookie,
+                   ApkAssetsCookieToJavaCookie(cookie));
+  env->SetIntField(out_typed_value, gTypedValueOffsets.mData, value.data);
+  env->SetObjectField(out_typed_value, gTypedValueOffsets.mString, nullptr);
+  env->SetIntField(out_typed_value, gTypedValueOffsets.mResourceId, ref);
+  env->SetIntField(out_typed_value, gTypedValueOffsets.mChangingConfigurations, type_spec_flags);
+  if (config != nullptr) {
+    env->SetIntField(out_typed_value, gTypedValueOffsets.mDensity, config->density);
+  }
+  return static_cast<jint>(ApkAssetsCookieToJavaCookie(cookie));
 }
 
 // ----------------------------------------------------------------------------
 
-/*
- * JNI registration.
- */
-static const JNINativeMethod gAssetManagerMethods[] = {
-    /* name, signature, funcPtr */
-
-    // Basic asset stuff.
-    { "openAsset",      "(Ljava/lang/String;I)J",
-        (void*) android_content_AssetManager_openAsset },
-    { "openAssetFd",      "(Ljava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
-        (void*) android_content_AssetManager_openAssetFd },
-    { "openNonAssetNative", "(ILjava/lang/String;I)J",
-        (void*) android_content_AssetManager_openNonAssetNative },
-    { "openNonAssetFdNative", "(ILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
-        (void*) android_content_AssetManager_openNonAssetFdNative },
-    { "list",           "(Ljava/lang/String;)[Ljava/lang/String;",
-        (void*) android_content_AssetManager_list },
-    { "destroyAsset",   "(J)V",
-        (void*) android_content_AssetManager_destroyAsset },
-    { "readAssetChar",  "(J)I",
-        (void*) android_content_AssetManager_readAssetChar },
-    { "readAsset",      "(J[BII)I",
-        (void*) android_content_AssetManager_readAsset },
-    { "seekAsset",      "(JJI)J",
-        (void*) android_content_AssetManager_seekAsset },
-    { "getAssetLength", "(J)J",
-        (void*) android_content_AssetManager_getAssetLength },
-    { "getAssetRemainingLength", "(J)J",
-        (void*) android_content_AssetManager_getAssetRemainingLength },
-    { "addAssetPathNative", "(Ljava/lang/String;Z)I",
-        (void*) android_content_AssetManager_addAssetPath },
-    { "addAssetFdNative", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)I",
-        (void*) android_content_AssetManager_addAssetFd },
-    { "addOverlayPathNative",   "(Ljava/lang/String;)I",
-        (void*) android_content_AssetManager_addOverlayPath },
-    { "isUpToDate",     "()Z",
-        (void*) android_content_AssetManager_isUpToDate },
-
-    // Resources.
-    { "getLocales",      "()[Ljava/lang/String;",
-        (void*) android_content_AssetManager_getLocales },
-    { "getNonSystemLocales", "()[Ljava/lang/String;",
-        (void*) android_content_AssetManager_getNonSystemLocales },
-    { "getSizeConfigurations", "()[Landroid/content/res/Configuration;",
-        (void*) android_content_AssetManager_getSizeConfigurations },
-    { "setConfiguration", "(IILjava/lang/String;IIIIIIIIIIIIIII)V",
-        (void*) android_content_AssetManager_setConfiguration },
-    { "getResourceIdentifier","(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
-        (void*) android_content_AssetManager_getResourceIdentifier },
-    { "getResourceName","(I)Ljava/lang/String;",
-        (void*) android_content_AssetManager_getResourceName },
-    { "getResourcePackageName","(I)Ljava/lang/String;",
-        (void*) android_content_AssetManager_getResourcePackageName },
-    { "getResourceTypeName","(I)Ljava/lang/String;",
-        (void*) android_content_AssetManager_getResourceTypeName },
-    { "getResourceEntryName","(I)Ljava/lang/String;",
-        (void*) android_content_AssetManager_getResourceEntryName },
-    { "loadResourceValue","(ISLandroid/util/TypedValue;Z)I",
-        (void*) android_content_AssetManager_loadResourceValue },
-    { "loadResourceBagValue","(IILandroid/util/TypedValue;Z)I",
-        (void*) android_content_AssetManager_loadResourceBagValue },
-    { "getStringBlockCount","()I",
-        (void*) android_content_AssetManager_getStringBlockCount },
-    { "getNativeStringBlock","(I)J",
-        (void*) android_content_AssetManager_getNativeStringBlock },
-    { "getCookieName","(I)Ljava/lang/String;",
-        (void*) android_content_AssetManager_getCookieName },
-    { "getAssignedPackageIdentifiers","()Landroid/util/SparseArray;",
-        (void*) android_content_AssetManager_getAssignedPackageIdentifiers },
-
-    // Themes.
-    { "newTheme", "()J",
-        (void*) android_content_AssetManager_newTheme },
-    { "deleteTheme", "(J)V",
-        (void*) android_content_AssetManager_deleteTheme },
-    { "applyThemeStyle", "(JIZ)V",
-        (void*) android_content_AssetManager_applyThemeStyle },
-    { "copyTheme", "(JJ)V",
-        (void*) android_content_AssetManager_copyTheme },
-    { "clearTheme", "(J)V",
-        (void*) android_content_AssetManager_clearTheme },
-    { "loadThemeAttributeValue", "(JILandroid/util/TypedValue;Z)I",
-        (void*) android_content_AssetManager_loadThemeAttributeValue },
-    { "getThemeChangingConfigurations", "(J)I",
-        (void*) android_content_AssetManager_getThemeChangingConfigurations },
-    { "dumpTheme", "(JILjava/lang/String;Ljava/lang/String;)V",
-        (void*) android_content_AssetManager_dumpTheme },
-    { "applyStyle","(JIIJ[IIJJ)V",
-        (void*) android_content_AssetManager_applyStyle },
-    { "resolveAttrs","(JII[I[I[I[I)Z",
-        (void*) android_content_AssetManager_resolveAttrs },
-    { "retrieveAttributes","(J[I[I[I)Z",
-        (void*) android_content_AssetManager_retrieveAttributes },
-    { "getArraySize","(I)I",
-        (void*) android_content_AssetManager_getArraySize },
-    { "retrieveArray","(I[I)I",
-        (void*) android_content_AssetManager_retrieveArray },
-
-    // XML files.
-    { "openXmlAssetNative", "(ILjava/lang/String;)J",
-        (void*) android_content_AssetManager_openXmlAssetNative },
-
-    // Arrays.
-    { "getArrayStringResource","(I)[Ljava/lang/String;",
-        (void*) android_content_AssetManager_getArrayStringResource },
-    { "getArrayStringInfo","(I)[I",
-        (void*) android_content_AssetManager_getArrayStringInfo },
-    { "getArrayIntResource","(I)[I",
-        (void*) android_content_AssetManager_getArrayIntResource },
-    { "getStyleAttributes","(I)[I",
-        (void*) android_content_AssetManager_getStyleAttributes },
-
-    // Bookkeeping.
-    { "init",           "(Z)V",
-        (void*) android_content_AssetManager_init },
-    { "destroy",        "()V",
-        (void*) android_content_AssetManager_destroy },
-    { "getGlobalAssetCount", "()I",
-        (void*) android_content_AssetManager_getGlobalAssetCount },
-    { "getAssetAllocations", "()Ljava/lang/String;",
-        (void*) android_content_AssetManager_getAssetAllocations },
-    { "getGlobalAssetManagerCount", "()I",
-        (void*) android_content_AssetManager_getGlobalAssetManagerCount },
+// Let the opaque type AAssetManager refer to a guarded AssetManager2 instance.
+struct GuardedAssetManager : public ::AAssetManager {
+  Guarded<AssetManager2> guarded_assetmanager;
 };
 
-int register_android_content_AssetManager(JNIEnv* env)
-{
-    jclass typedValue = FindClassOrDie(env, "android/util/TypedValue");
-    gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I");
-    gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I");
-    gTypedValueOffsets.mString = GetFieldIDOrDie(env, typedValue, "string",
-                                                 "Ljava/lang/CharSequence;");
-    gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I");
-    gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I");
-    gTypedValueOffsets.mChangingConfigurations = GetFieldIDOrDie(env, typedValue,
-                                                                 "changingConfigurations", "I");
-    gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I");
+::AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) {
+  jlong assetmanager_handle = env->GetLongField(jassetmanager, gAssetManagerOffsets.mObject);
+  ::AAssetManager* am = reinterpret_cast<::AAssetManager*>(assetmanager_handle);
+  if (am == nullptr) {
+    jniThrowException(env, "java/lang/IllegalStateException", "AssetManager has been finalized!");
+    return nullptr;
+  }
+  return am;
+}
 
-    jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor");
-    gAssetFileDescriptorOffsets.mFd = GetFieldIDOrDie(env, assetFd, "mFd",
-                                                      "Landroid/os/ParcelFileDescriptor;");
-    gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J");
-    gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J");
+Guarded<AssetManager2>* AssetManagerForNdkAssetManager(::AAssetManager* assetmanager) {
+  if (assetmanager == nullptr) {
+    return nullptr;
+  }
+  return &reinterpret_cast<GuardedAssetManager*>(assetmanager)->guarded_assetmanager;
+}
 
-    jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager");
-    gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J");
+Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager) {
+  return AssetManagerForNdkAssetManager(NdkAssetManagerForJavaObject(env, jassetmanager));
+}
 
-    jclass stringClass = FindClassOrDie(env, "java/lang/String");
-    g_stringClass = MakeGlobalRefOrDie(env, stringClass);
+static Guarded<AssetManager2>& AssetManagerFromLong(jlong ptr) {
+  return *AssetManagerForNdkAssetManager(reinterpret_cast<AAssetManager*>(ptr));
+}
 
-    jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray");
-    gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass);
-    gSparseArrayOffsets.constructor = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject,
-                                                       "<init>", "()V");
-    gSparseArrayOffsets.put = GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put",
-                                               "(ILjava/lang/Object;)V");
+static jobject ReturnParcelFileDescriptor(JNIEnv* env, std::unique_ptr<Asset> asset,
+                                          jlongArray out_offsets) {
+  off64_t start_offset, length;
+  int fd = asset->openFileDescriptor(&start_offset, &length);
+  asset.reset();
 
-    jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration");
-    gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass);
-    gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass,
-            "<init>", "()V");
-    gConfigurationOffsets.mSmallestScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass,
-            "smallestScreenWidthDp", "I");
-    gConfigurationOffsets.mScreenWidthDpOffset = GetFieldIDOrDie(env, configurationClass,
-            "screenWidthDp", "I");
-    gConfigurationOffsets.mScreenHeightDpOffset = GetFieldIDOrDie(env, configurationClass,
-            "screenHeightDp", "I");
+  if (fd < 0) {
+    jniThrowException(env, "java/io/FileNotFoundException",
+                      "This file can not be opened as a file descriptor; it is probably "
+                      "compressed");
+    return nullptr;
+  }
 
-    return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods,
-                                NELEM(gAssetManagerMethods));
+  jlong* offsets = reinterpret_cast<jlong*>(env->GetPrimitiveArrayCritical(out_offsets, 0));
+  if (offsets == nullptr) {
+    close(fd);
+    return nullptr;
+  }
+
+  offsets[0] = start_offset;
+  offsets[1] = length;
+
+  env->ReleasePrimitiveArrayCritical(out_offsets, offsets, 0);
+
+  jobject file_desc = jniCreateFileDescriptor(env, fd);
+  if (file_desc == nullptr) {
+    close(fd);
+    return nullptr;
+  }
+  return newParcelFileDescriptor(env, file_desc);
+}
+
+static jint NativeGetGlobalAssetCount(JNIEnv* /*env*/, jobject /*clazz*/) {
+  return Asset::getGlobalCount();
+}
+
+static jobject NativeGetAssetAllocations(JNIEnv* env, jobject /*clazz*/) {
+  String8 alloc = Asset::getAssetAllocations();
+  if (alloc.length() <= 0) {
+    return nullptr;
+  }
+  return env->NewStringUTF(alloc.string());
+}
+
+static jint NativeGetGlobalAssetManagerCount(JNIEnv* /*env*/, jobject /*clazz*/) {
+  // TODO(adamlesinski): Switch to AssetManager2.
+  return AssetManager::getGlobalCount();
+}
+
+static jlong NativeCreate(JNIEnv* /*env*/, jclass /*clazz*/) {
+  // AssetManager2 needs to be protected by a lock. To avoid cache misses, we allocate the lock and
+  // AssetManager2 in a contiguous block (GuardedAssetManager).
+  return reinterpret_cast<jlong>(new GuardedAssetManager());
+}
+
+static void NativeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+  delete reinterpret_cast<GuardedAssetManager*>(ptr);
+}
+
+static void NativeSetApkAssets(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+                               jobjectArray apk_assets_array, jboolean invalidate_caches) {
+  const jsize apk_assets_len = env->GetArrayLength(apk_assets_array);
+  std::vector<const ApkAssets*> apk_assets;
+  apk_assets.reserve(apk_assets_len);
+  for (jsize i = 0; i < apk_assets_len; i++) {
+    jobject obj = env->GetObjectArrayElement(apk_assets_array, i);
+    if (obj == nullptr) {
+      std::string msg = StringPrintf("ApkAssets at index %d is null", i);
+      jniThrowNullPointerException(env, msg.c_str());
+      return;
+    }
+
+    jlong apk_assets_native_ptr = env->GetLongField(obj, gApkAssetsFields.native_ptr);
+    if (env->ExceptionCheck()) {
+      return;
+    }
+    apk_assets.push_back(reinterpret_cast<const ApkAssets*>(apk_assets_native_ptr));
+  }
+
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  assetmanager->SetApkAssets(apk_assets, invalidate_caches);
+}
+
+static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc,
+                                   jstring locale, jint orientation, jint touchscreen, jint density,
+                                   jint keyboard, jint keyboard_hidden, jint navigation,
+                                   jint screen_width, jint screen_height,
+                                   jint smallest_screen_width_dp, jint screen_width_dp,
+                                   jint screen_height_dp, jint screen_layout, jint ui_mode,
+                                   jint color_mode, jint major_version) {
+  ResTable_config configuration;
+  memset(&configuration, 0, sizeof(configuration));
+  configuration.mcc = static_cast<uint16_t>(mcc);
+  configuration.mnc = static_cast<uint16_t>(mnc);
+  configuration.orientation = static_cast<uint8_t>(orientation);
+  configuration.touchscreen = static_cast<uint8_t>(touchscreen);
+  configuration.density = static_cast<uint16_t>(density);
+  configuration.keyboard = static_cast<uint8_t>(keyboard);
+  configuration.inputFlags = static_cast<uint8_t>(keyboard_hidden);
+  configuration.navigation = static_cast<uint8_t>(navigation);
+  configuration.screenWidth = static_cast<uint16_t>(screen_width);
+  configuration.screenHeight = static_cast<uint16_t>(screen_height);
+  configuration.smallestScreenWidthDp = static_cast<uint16_t>(smallest_screen_width_dp);
+  configuration.screenWidthDp = static_cast<uint16_t>(screen_width_dp);
+  configuration.screenHeightDp = static_cast<uint16_t>(screen_height_dp);
+  configuration.screenLayout = static_cast<uint8_t>(screen_layout);
+  configuration.uiMode = static_cast<uint8_t>(ui_mode);
+  configuration.colorMode = static_cast<uint8_t>(color_mode);
+  configuration.sdkVersion = static_cast<uint16_t>(major_version);
+
+  if (locale != nullptr) {
+    ScopedUtfChars locale_utf8(env, locale);
+    CHECK(locale_utf8.c_str() != nullptr);
+    configuration.setBcp47Locale(locale_utf8.c_str());
+  }
+
+  // Constants duplicated from Java class android.content.res.Configuration.
+  static const jint kScreenLayoutRoundMask = 0x300;
+  static const jint kScreenLayoutRoundShift = 8;
+
+  // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
+  // in C++. We must extract the round qualifier out of the Java screenLayout and put it
+  // into screenLayout2.
+  configuration.screenLayout2 =
+      static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
+
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  assetmanager->SetConfiguration(configuration);
+}
+
+static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+
+  jobject sparse_array =
+        env->NewObject(gSparseArrayOffsets.classObject, gSparseArrayOffsets.constructor);
+
+  if (sparse_array == nullptr) {
+    // An exception is pending.
+    return nullptr;
+  }
+
+  assetmanager->ForEachPackage([&](const std::string& package_name, uint8_t package_id) {
+    jstring jpackage_name = env->NewStringUTF(package_name.c_str());
+    if (jpackage_name == nullptr) {
+      // An exception is pending.
+      return;
+    }
+
+    env->CallVoidMethod(sparse_array, gSparseArrayOffsets.put, static_cast<jint>(package_id),
+                        jpackage_name);
+  });
+  return sparse_array;
+}
+
+static jobjectArray NativeList(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring path) {
+  ScopedUtfChars path_utf8(env, path);
+  if (path_utf8.c_str() == nullptr) {
+    // This will throw NPE.
+    return nullptr;
+  }
+
+  std::vector<std::string> all_file_paths;
+  {
+    StringPiece normalized_path = path_utf8.c_str();
+    if (normalized_path.data()[0] == '/') {
+      normalized_path = normalized_path.substr(1);
+    }
+    std::string root_path = StringPrintf("assets/%s", normalized_path.data());
+    ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+    for (const ApkAssets* assets : assetmanager->GetApkAssets()) {
+      assets->ForEachFile(root_path, [&](const StringPiece& file_path, FileType type) {
+        if (type == FileType::kFileTypeRegular) {
+          all_file_paths.push_back(file_path.to_string());
+        }
+      });
+    }
+  }
+
+  jobjectArray array = env->NewObjectArray(all_file_paths.size(), g_stringClass, nullptr);
+  if (array == nullptr) {
+    return nullptr;
+  }
+
+  jsize index = 0;
+  for (const std::string& file_path : all_file_paths) {
+    jstring java_string = env->NewStringUTF(file_path.c_str());
+
+    // Check for errors creating the strings (if malformed or no memory).
+    if (env->ExceptionCheck()) {
+     return nullptr;
+    }
+
+    env->SetObjectArrayElement(array, index++, java_string);
+
+    // If we have a large amount of string in our array, we might overflow the
+    // local reference table of the VM.
+    env->DeleteLocalRef(java_string);
+  }
+  return array;
+}
+
+static jlong NativeOpenAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path,
+                             jint access_mode) {
+  ScopedUtfChars asset_path_utf8(env, asset_path);
+  if (asset_path_utf8.c_str() == nullptr) {
+    // This will throw NPE.
+    return 0;
+  }
+
+  if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM &&
+      access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) {
+    jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
+    return 0;
+  }
+
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  std::unique_ptr<Asset> asset =
+      assetmanager->Open(asset_path_utf8.c_str(), static_cast<Asset::AccessMode>(access_mode));
+  if (!asset) {
+    jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+    return 0;
+  }
+  return reinterpret_cast<jlong>(asset.release());
+}
+
+static jobject NativeOpenAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring asset_path,
+                                 jlongArray out_offsets) {
+  ScopedUtfChars asset_path_utf8(env, asset_path);
+  if (asset_path_utf8.c_str() == nullptr) {
+    // This will throw NPE.
+    return nullptr;
+  }
+
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  std::unique_ptr<Asset> asset = assetmanager->Open(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM);
+  if (!asset) {
+    jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+    return nullptr;
+  }
+  return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets);
+}
+
+static jlong NativeOpenNonAsset(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie,
+                                jstring asset_path, jint access_mode) {
+  ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+  ScopedUtfChars asset_path_utf8(env, asset_path);
+  if (asset_path_utf8.c_str() == nullptr) {
+    // This will throw NPE.
+    return 0;
+  }
+
+  if (access_mode != Asset::ACCESS_UNKNOWN && access_mode != Asset::ACCESS_RANDOM &&
+      access_mode != Asset::ACCESS_STREAMING && access_mode != Asset::ACCESS_BUFFER) {
+    jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode");
+    return 0;
+  }
+
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  std::unique_ptr<Asset> asset;
+  if (cookie != kInvalidCookie) {
+    asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie,
+                                       static_cast<Asset::AccessMode>(access_mode));
+  } else {
+    asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(),
+                                       static_cast<Asset::AccessMode>(access_mode));
+  }
+
+  if (!asset) {
+    jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+    return 0;
+  }
+  return reinterpret_cast<jlong>(asset.release());
+}
+
+static jobject NativeOpenNonAssetFd(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint jcookie,
+                                    jstring asset_path, jlongArray out_offsets) {
+  ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+  ScopedUtfChars asset_path_utf8(env, asset_path);
+  if (asset_path_utf8.c_str() == nullptr) {
+    // This will throw NPE.
+    return nullptr;
+  }
+
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  std::unique_ptr<Asset> asset;
+  if (cookie != kInvalidCookie) {
+    asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM);
+  } else {
+    asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM);
+  }
+
+  if (!asset) {
+    jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+    return nullptr;
+  }
+  return ReturnParcelFileDescriptor(env, std::move(asset), out_offsets);
+}
+
+static jlong NativeOpenXmlAsset(JNIEnv* env, jobject /*clazz*/, jlong ptr, jint jcookie,
+                                jstring asset_path) {
+  ApkAssetsCookie cookie = JavaCookieToApkAssetsCookie(jcookie);
+  ScopedUtfChars asset_path_utf8(env, asset_path);
+  if (asset_path_utf8.c_str() == nullptr) {
+    // This will throw NPE.
+    return 0;
+  }
+
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  std::unique_ptr<Asset> asset;
+  if (cookie != kInvalidCookie) {
+    asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), cookie, Asset::ACCESS_RANDOM);
+  } else {
+    asset = assetmanager->OpenNonAsset(asset_path_utf8.c_str(), Asset::ACCESS_RANDOM, &cookie);
+  }
+
+  if (!asset) {
+    jniThrowException(env, "java/io/FileNotFoundException", asset_path_utf8.c_str());
+    return 0;
+  }
+
+  // May be nullptr.
+  const DynamicRefTable* dynamic_ref_table = assetmanager->GetDynamicRefTableForCookie(cookie);
+
+  std::unique_ptr<ResXMLTree> xml_tree = util::make_unique<ResXMLTree>(dynamic_ref_table);
+  status_t err = xml_tree->setTo(asset->getBuffer(true), asset->getLength(), true);
+  asset.reset();
+
+  if (err != NO_ERROR) {
+    jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file");
+    return 0;
+  }
+  return reinterpret_cast<jlong>(xml_tree.release());
+}
+
+static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
+                                   jshort density, jobject typed_value,
+                                   jboolean resolve_references) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  Res_value value;
+  ResTable_config selected_config;
+  uint32_t flags;
+  ApkAssetsCookie cookie =
+      assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
+                                static_cast<uint16_t>(density), &value, &selected_config, &flags);
+  if (cookie == kInvalidCookie) {
+    return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+  }
+
+  uint32_t ref = static_cast<uint32_t>(resid);
+  if (resolve_references) {
+    cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &flags, &ref);
+    if (cookie == kInvalidCookie) {
+      return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+    }
+  }
+  return CopyValue(env, cookie, value, ref, flags, &selected_config, typed_value);
+}
+
+static jint NativeGetResourceBagValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
+                                      jint bag_entry_id, jobject typed_value) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+  if (bag == nullptr) {
+    return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+  }
+
+  uint32_t type_spec_flags = bag->type_spec_flags;
+  ApkAssetsCookie cookie = kInvalidCookie;
+  const Res_value* bag_value = nullptr;
+  for (const ResolvedBag::Entry& entry : bag) {
+    if (entry.key == static_cast<uint32_t>(bag_entry_id)) {
+      cookie = entry.cookie;
+      bag_value = &entry.value;
+
+      // Keep searching (the old implementation did that).
+    }
+  }
+
+  if (cookie == kInvalidCookie) {
+    return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+  }
+
+  Res_value value = *bag_value;
+  uint32_t ref = static_cast<uint32_t>(resid);
+  ResTable_config selected_config;
+  cookie = assetmanager->ResolveReference(cookie, &value, &selected_config, &type_spec_flags, &ref);
+  if (cookie == kInvalidCookie) {
+    return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+  }
+  return CopyValue(env, cookie, value, ref, type_spec_flags, nullptr, typed_value);
+}
+
+static jintArray NativeGetStyleAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+  if (bag == nullptr) {
+    return nullptr;
+  }
+
+  jintArray array = env->NewIntArray(bag->entry_count);
+  if (env->ExceptionCheck()) {
+    return nullptr;
+  }
+
+  for (uint32_t i = 0; i < bag->entry_count; i++) {
+    jint attr_resid = bag->entries[i].key;
+    env->SetIntArrayRegion(array, i, 1, &attr_resid);
+  }
+  return array;
+}
+
+static jobjectArray NativeGetResourceStringArray(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+                                                 jint resid) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+  if (bag == nullptr) {
+    return nullptr;
+  }
+
+  jobjectArray array = env->NewObjectArray(bag->entry_count, g_stringClass, nullptr);
+  if (array == nullptr) {
+    return nullptr;
+  }
+
+  for (uint32_t i = 0; i < bag->entry_count; i++) {
+    const ResolvedBag::Entry& entry = bag->entries[i];
+
+    // Resolve any references to their final value.
+    Res_value value = entry.value;
+    ResTable_config selected_config;
+    uint32_t flags;
+    uint32_t ref;
+    ApkAssetsCookie cookie =
+        assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+    if (cookie == kInvalidCookie) {
+      return nullptr;
+    }
+
+    if (value.dataType == Res_value::TYPE_STRING) {
+      const ApkAssets* apk_assets = assetmanager->GetApkAssets()[cookie];
+      const ResStringPool* pool = apk_assets->GetLoadedArsc()->GetStringPool();
+
+      jstring java_string = nullptr;
+      size_t str_len;
+      const char* str_utf8 = pool->string8At(value.data, &str_len);
+      if (str_utf8 != nullptr) {
+        java_string = env->NewStringUTF(str_utf8);
+      } else {
+        const char16_t* str_utf16 = pool->stringAt(value.data, &str_len);
+        java_string = env->NewString(reinterpret_cast<const jchar*>(str_utf16), str_len);
+      }
+
+      // Check for errors creating the strings (if malformed or no memory).
+      if (env->ExceptionCheck()) {
+        return nullptr;
+      }
+
+      env->SetObjectArrayElement(array, i, java_string);
+
+      // If we have a large amount of string in our array, we might overflow the
+      // local reference table of the VM.
+      env->DeleteLocalRef(java_string);
+    }
+  }
+  return array;
+}
+
+static jintArray NativeGetResourceStringArrayInfo(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+                                                  jint resid) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+  if (bag == nullptr) {
+    return nullptr;
+  }
+
+  jintArray array = env->NewIntArray(bag->entry_count * 2);
+  if (array == nullptr) {
+    return nullptr;
+  }
+
+  jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr));
+  if (buffer == nullptr) {
+    return nullptr;
+  }
+
+  for (size_t i = 0; i < bag->entry_count; i++) {
+    const ResolvedBag::Entry& entry = bag->entries[i];
+    Res_value value = entry.value;
+    ResTable_config selected_config;
+    uint32_t flags;
+    uint32_t ref;
+    ApkAssetsCookie cookie =
+        assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+    if (cookie == kInvalidCookie) {
+      env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT);
+      return nullptr;
+    }
+
+    jint string_index = -1;
+    if (value.dataType == Res_value::TYPE_STRING) {
+      string_index = static_cast<jint>(value.data);
+    }
+
+    buffer[i * 2] = ApkAssetsCookieToJavaCookie(cookie);
+    buffer[(i * 2) + 1] = string_index;
+  }
+  env->ReleasePrimitiveArrayCritical(array, buffer, 0);
+  return array;
+}
+
+static jintArray NativeGetResourceIntArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+  if (bag == nullptr) {
+    return nullptr;
+  }
+
+  jintArray array = env->NewIntArray(bag->entry_count);
+  if (array == nullptr) {
+    return nullptr;
+  }
+
+  jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(array, nullptr));
+  if (buffer == nullptr) {
+    return nullptr;
+  }
+
+  for (size_t i = 0; i < bag->entry_count; i++) {
+    const ResolvedBag::Entry& entry = bag->entries[i];
+    Res_value value = entry.value;
+    ResTable_config selected_config;
+    uint32_t flags;
+    uint32_t ref;
+    ApkAssetsCookie cookie =
+        assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+    if (cookie == kInvalidCookie) {
+      env->ReleasePrimitiveArrayCritical(array, buffer, JNI_ABORT);
+      return nullptr;
+    }
+
+    if (value.dataType >= Res_value::TYPE_FIRST_INT && value.dataType <= Res_value::TYPE_LAST_INT) {
+      buffer[i] = static_cast<jint>(value.data);
+    }
+  }
+  env->ReleasePrimitiveArrayCritical(array, buffer, 0);
+  return array;
+}
+
+static jint NativeGetResourceArraySize(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jint resid) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+  if (bag == nullptr) {
+    return -1;
+  }
+  return static_cast<jint>(bag->entry_count);
+}
+
+static jint NativeGetResourceArray(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid,
+                                   jintArray out_data) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  const ResolvedBag* bag = assetmanager->GetBag(static_cast<uint32_t>(resid));
+  if (bag == nullptr) {
+    return -1;
+  }
+
+  const jsize out_data_length = env->GetArrayLength(out_data);
+  if (env->ExceptionCheck()) {
+    return -1;
+  }
+
+  if (static_cast<jsize>(bag->entry_count) > out_data_length * STYLE_NUM_ENTRIES) {
+    jniThrowException(env, "java/lang/IllegalArgumentException", "Input array is not large enough");
+    return -1;
+  }
+
+  jint* buffer = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_data, nullptr));
+  if (buffer == nullptr) {
+    return -1;
+  }
+
+  jint* cursor = buffer;
+  for (size_t i = 0; i < bag->entry_count; i++) {
+    const ResolvedBag::Entry& entry = bag->entries[i];
+    Res_value value = entry.value;
+    ResTable_config selected_config;
+    selected_config.density = 0;
+    uint32_t flags = bag->type_spec_flags;
+    uint32_t ref;
+    ApkAssetsCookie cookie =
+        assetmanager->ResolveReference(entry.cookie, &value, &selected_config, &flags, &ref);
+    if (cookie == kInvalidCookie) {
+      env->ReleasePrimitiveArrayCritical(out_data, buffer, JNI_ABORT);
+      return -1;
+    }
+
+    // Deal with the special @null value -- it turns back to TYPE_NULL.
+    if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
+      value.dataType = Res_value::TYPE_NULL;
+      value.data = Res_value::DATA_NULL_UNDEFINED;
+    }
+
+    cursor[STYLE_TYPE] = static_cast<jint>(value.dataType);
+    cursor[STYLE_DATA] = static_cast<jint>(value.data);
+    cursor[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
+    cursor[STYLE_RESOURCE_ID] = static_cast<jint>(ref);
+    cursor[STYLE_CHANGING_CONFIGURATIONS] = static_cast<jint>(flags);
+    cursor[STYLE_DENSITY] = static_cast<jint>(selected_config.density);
+    cursor += STYLE_NUM_ENTRIES;
+  }
+  env->ReleasePrimitiveArrayCritical(out_data, buffer, 0);
+  return static_cast<jint>(bag->entry_count);
+}
+
+static jint NativeGetResourceIdentifier(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring name,
+                                        jstring def_type, jstring def_package) {
+  ScopedUtfChars name_utf8(env, name);
+  if (name_utf8.c_str() == nullptr) {
+    // This will throw NPE.
+    return 0;
+  }
+
+  std::string type;
+  if (def_type != nullptr) {
+    ScopedUtfChars type_utf8(env, def_type);
+    CHECK(type_utf8.c_str() != nullptr);
+    type = type_utf8.c_str();
+  }
+
+  std::string package;
+  if (def_package != nullptr) {
+    ScopedUtfChars package_utf8(env, def_package);
+    CHECK(package_utf8.c_str() != nullptr);
+    package = package_utf8.c_str();
+  }
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  return static_cast<jint>(assetmanager->GetResourceId(name_utf8.c_str(), type, package));
+}
+
+static jstring NativeGetResourceName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  AssetManager2::ResourceName name;
+  if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+    return nullptr;
+  }
+
+  std::string result;
+  if (name.package != nullptr) {
+    result.append(name.package, name.package_len);
+  }
+
+  if (name.type != nullptr || name.type16 != nullptr) {
+    if (!result.empty()) {
+      result += ":";
+    }
+
+    if (name.type != nullptr) {
+      result.append(name.type, name.type_len);
+    } else {
+      result += util::Utf16ToUtf8(StringPiece16(name.type16, name.type_len));
+    }
+  }
+
+  if (name.entry != nullptr || name.entry16 != nullptr) {
+    if (!result.empty()) {
+      result += "/";
+    }
+
+    if (name.entry != nullptr) {
+      result.append(name.entry, name.entry_len);
+    } else {
+      result += util::Utf16ToUtf8(StringPiece16(name.entry16, name.entry_len));
+    }
+  }
+  return env->NewStringUTF(result.c_str());
+}
+
+static jstring NativeGetResourcePackageName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  AssetManager2::ResourceName name;
+  if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+    return nullptr;
+  }
+
+  if (name.package != nullptr) {
+    return env->NewStringUTF(name.package);
+  }
+  return nullptr;
+}
+
+static jstring NativeGetResourceTypeName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  AssetManager2::ResourceName name;
+  if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+    return nullptr;
+  }
+
+  if (name.type != nullptr) {
+    return env->NewStringUTF(name.type);
+  } else if (name.type16 != nullptr) {
+    return env->NewString(reinterpret_cast<const jchar*>(name.type16), name.type_len);
+  }
+  return nullptr;
+}
+
+static jstring NativeGetResourceEntryName(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint resid) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  AssetManager2::ResourceName name;
+  if (!assetmanager->GetResourceName(static_cast<uint32_t>(resid), &name)) {
+    return nullptr;
+  }
+
+  if (name.entry != nullptr) {
+    return env->NewStringUTF(name.entry);
+  } else if (name.entry16 != nullptr) {
+    return env->NewString(reinterpret_cast<const jchar*>(name.entry16), name.entry_len);
+  }
+  return nullptr;
+}
+
+static jobjectArray NativeGetLocales(JNIEnv* env, jclass /*class*/, jlong ptr,
+                                     jboolean exclude_system) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  std::set<std::string> locales =
+      assetmanager->GetResourceLocales(exclude_system, true /*merge_equivalent_languages*/);
+
+  jobjectArray array = env->NewObjectArray(locales.size(), g_stringClass, nullptr);
+  if (array == nullptr) {
+    return nullptr;
+  }
+
+  size_t idx = 0;
+  for (const std::string& locale : locales) {
+    jstring java_string = env->NewStringUTF(locale.c_str());
+    if (java_string == nullptr) {
+      return nullptr;
+    }
+    env->SetObjectArrayElement(array, idx++, java_string);
+    env->DeleteLocalRef(java_string);
+  }
+  return array;
+}
+
+static jobject ConstructConfigurationObject(JNIEnv* env, const ResTable_config& config) {
+  jobject result =
+      env->NewObject(gConfigurationOffsets.classObject, gConfigurationOffsets.constructor);
+  if (result == nullptr) {
+    return nullptr;
+  }
+
+  env->SetIntField(result, gConfigurationOffsets.mSmallestScreenWidthDpOffset,
+                   config.smallestScreenWidthDp);
+  env->SetIntField(result, gConfigurationOffsets.mScreenWidthDpOffset, config.screenWidthDp);
+  env->SetIntField(result, gConfigurationOffsets.mScreenHeightDpOffset, config.screenHeightDp);
+  return result;
+}
+
+static jobjectArray NativeGetSizeConfigurations(JNIEnv* env, jclass /*clazz*/, jlong ptr) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  std::set<ResTable_config> configurations =
+      assetmanager->GetResourceConfigurations(true /*exclude_system*/, false /*exclude_mipmap*/);
+
+  jobjectArray array =
+      env->NewObjectArray(configurations.size(), gConfigurationOffsets.classObject, nullptr);
+  if (array == nullptr) {
+    return nullptr;
+  }
+
+  size_t idx = 0;
+  for (const ResTable_config& configuration : configurations) {
+    jobject java_configuration = ConstructConfigurationObject(env, configuration);
+    if (java_configuration == nullptr) {
+      return nullptr;
+    }
+
+    env->SetObjectArrayElement(array, idx++, java_configuration);
+    env->DeleteLocalRef(java_configuration);
+  }
+  return array;
+}
+
+static void NativeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+                             jint def_style_attr, jint def_style_resid, jlong xml_parser_ptr,
+                             jintArray java_attrs, jlong out_values_ptr, jlong out_indices_ptr) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+  CHECK(theme->GetAssetManager() == &(*assetmanager));
+  (void) assetmanager;
+
+  ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
+  uint32_t* out_values = reinterpret_cast<uint32_t*>(out_values_ptr);
+  uint32_t* out_indices = reinterpret_cast<uint32_t*>(out_indices_ptr);
+
+  jsize attrs_len = env->GetArrayLength(java_attrs);
+  jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
+  if (attrs == nullptr) {
+    return;
+  }
+
+  ApplyStyle(theme, xml_parser, static_cast<uint32_t>(def_style_attr),
+             static_cast<uint32_t>(def_style_resid), reinterpret_cast<uint32_t*>(attrs), attrs_len,
+             out_values, out_indices);
+  env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+}
+
+static jboolean NativeResolveAttrs(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+                                   jint def_style_attr, jint def_style_resid, jintArray java_values,
+                                   jintArray java_attrs, jintArray out_java_values,
+                                   jintArray out_java_indices) {
+  const jsize attrs_len = env->GetArrayLength(java_attrs);
+  const jsize out_values_len = env->GetArrayLength(out_java_values);
+  if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) {
+    jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small");
+    return JNI_FALSE;
+  }
+
+  jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
+  if (attrs == nullptr) {
+    return JNI_FALSE;
+  }
+
+  jint* values = nullptr;
+  jsize values_len = 0;
+  if (java_values != nullptr) {
+    values_len = env->GetArrayLength(java_values);
+    values = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_values, nullptr));
+    if (values == nullptr) {
+      env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+      return JNI_FALSE;
+    }
+  }
+
+  jint* out_values =
+      reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr));
+  if (out_values == nullptr) {
+    env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+    if (values != nullptr) {
+      env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT);
+    }
+    return JNI_FALSE;
+  }
+
+  jint* out_indices = nullptr;
+  if (out_java_indices != nullptr) {
+    jsize out_indices_len = env->GetArrayLength(out_java_indices);
+    if (out_indices_len > attrs_len) {
+      out_indices =
+          reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr));
+      if (out_indices == nullptr) {
+        env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+        if (values != nullptr) {
+          env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT);
+        }
+        env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT);
+        return JNI_FALSE;
+      }
+    }
+  }
+
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+  CHECK(theme->GetAssetManager() == &(*assetmanager));
+  (void) assetmanager;
+
+  bool result = ResolveAttrs(
+      theme, static_cast<uint32_t>(def_style_attr), static_cast<uint32_t>(def_style_resid),
+      reinterpret_cast<uint32_t*>(values), values_len, reinterpret_cast<uint32_t*>(attrs),
+      attrs_len, reinterpret_cast<uint32_t*>(out_values), reinterpret_cast<uint32_t*>(out_indices));
+  if (out_indices != nullptr) {
+    env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0);
+  }
+
+  env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0);
+  if (values != nullptr) {
+    env->ReleasePrimitiveArrayCritical(java_values, values, JNI_ABORT);
+  }
+  env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+  return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jboolean NativeRetrieveAttributes(JNIEnv* env, jclass /*clazz*/, jlong ptr,
+                                         jlong xml_parser_ptr, jintArray java_attrs,
+                                         jintArray out_java_values, jintArray out_java_indices) {
+  const jsize attrs_len = env->GetArrayLength(java_attrs);
+  const jsize out_values_len = env->GetArrayLength(out_java_values);
+  if (out_values_len < (attrs_len * STYLE_NUM_ENTRIES)) {
+    jniThrowException(env, "java/lang/IndexOutOfBoundsException", "outValues too small");
+    return JNI_FALSE;
+  }
+
+  jint* attrs = reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(java_attrs, nullptr));
+  if (attrs == nullptr) {
+    return JNI_FALSE;
+  }
+
+  jint* out_values =
+      reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_values, nullptr));
+  if (out_values == nullptr) {
+    env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+    return JNI_FALSE;
+  }
+
+  jint* out_indices = nullptr;
+  if (out_java_indices != nullptr) {
+    jsize out_indices_len = env->GetArrayLength(out_java_indices);
+    if (out_indices_len > attrs_len) {
+      out_indices =
+          reinterpret_cast<jint*>(env->GetPrimitiveArrayCritical(out_java_indices, nullptr));
+      if (out_indices == nullptr) {
+        env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+        env->ReleasePrimitiveArrayCritical(out_java_values, out_values, JNI_ABORT);
+        return JNI_FALSE;
+      }
+    }
+  }
+
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
+
+  bool result = RetrieveAttributes(assetmanager.get(), xml_parser,
+                                   reinterpret_cast<uint32_t*>(attrs), attrs_len,
+                                   reinterpret_cast<uint32_t*>(out_values),
+                                   reinterpret_cast<uint32_t*>(out_indices));
+
+  if (out_indices != nullptr) {
+    env->ReleasePrimitiveArrayCritical(out_java_indices, out_indices, 0);
+  }
+  env->ReleasePrimitiveArrayCritical(out_java_values, out_values, 0);
+  env->ReleasePrimitiveArrayCritical(java_attrs, attrs, JNI_ABORT);
+  return result ? JNI_TRUE : JNI_FALSE;
+}
+
+static jlong NativeThemeCreate(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  return reinterpret_cast<jlong>(assetmanager->NewTheme().release());
+}
+
+static void NativeThemeDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) {
+  delete reinterpret_cast<Theme*>(theme_ptr);
+}
+
+static void NativeThemeApplyStyle(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+                                  jint resid, jboolean force) {
+  // AssetManager is accessed via the theme, so grab an explicit lock here.
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+  CHECK(theme->GetAssetManager() == &(*assetmanager));
+  (void) assetmanager;
+  theme->ApplyStyle(static_cast<uint32_t>(resid), force);
+
+  // TODO(adamlesinski): Consider surfacing exception when result is failure.
+  // CTS currently expects no exceptions from this method.
+  // std::string error_msg = StringPrintf("Failed to apply style 0x%08x to theme", resid);
+  // jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
+}
+
+static void NativeThemeCopy(JNIEnv* env, jclass /*clazz*/, jlong dst_theme_ptr,
+                            jlong src_theme_ptr) {
+  Theme* dst_theme = reinterpret_cast<Theme*>(dst_theme_ptr);
+  Theme* src_theme = reinterpret_cast<Theme*>(src_theme_ptr);
+  if (!dst_theme->SetTo(*src_theme)) {
+    jniThrowException(env, "java/lang/IllegalArgumentException",
+                      "Themes are from different AssetManagers");
+  }
+}
+
+static void NativeThemeClear(JNIEnv* /*env*/, jclass /*clazz*/, jlong theme_ptr) {
+  reinterpret_cast<Theme*>(theme_ptr)->Clear();
+}
+
+static jint NativeThemeGetAttributeValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+                                         jint resid, jobject typed_value,
+                                         jboolean resolve_references) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+  CHECK(theme->GetAssetManager() == &(*assetmanager));
+  (void) assetmanager;
+
+  Res_value value;
+  uint32_t flags;
+  ApkAssetsCookie cookie = theme->GetAttribute(static_cast<uint32_t>(resid), &value, &flags);
+  if (cookie == kInvalidCookie) {
+    return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+  }
+
+  uint32_t ref = 0u;
+  if (resolve_references) {
+    ResTable_config selected_config;
+    cookie =
+        theme->GetAssetManager()->ResolveReference(cookie, &value, &selected_config, &flags, &ref);
+    if (cookie == kInvalidCookie) {
+      return ApkAssetsCookieToJavaCookie(kInvalidCookie);
+    }
+  }
+  return CopyValue(env, cookie, value, ref, flags, nullptr, typed_value);
+}
+
+static void NativeThemeDump(JNIEnv* /*env*/, jclass /*clazz*/, jlong ptr, jlong theme_ptr,
+                            jint priority, jstring tag, jstring prefix) {
+  ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
+  Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+  CHECK(theme->GetAssetManager() == &(*assetmanager));
+  (void) assetmanager;
+  (void) theme;
+  (void) priority;
+  (void) tag;
+  (void) prefix;
+}
+
+static jint NativeThemeGetChangingConfigurations(JNIEnv* /*env*/, jclass /*clazz*/,
+                                                 jlong theme_ptr) {
+  Theme* theme = reinterpret_cast<Theme*>(theme_ptr);
+  return static_cast<jint>(theme->GetChangingConfigurations());
+}
+
+static void NativeAssetDestroy(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+  delete reinterpret_cast<Asset*>(asset_ptr);
+}
+
+static jint NativeAssetReadChar(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+  Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+  uint8_t b;
+  ssize_t res = asset->read(&b, sizeof(b));
+  return res == sizeof(b) ? static_cast<jint>(b) : -1;
+}
+
+static jint NativeAssetRead(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jbyteArray java_buffer,
+                            jint offset, jint len) {
+  if (len == 0) {
+    return 0;
+  }
+
+  jsize buffer_len = env->GetArrayLength(java_buffer);
+  if (offset < 0 || offset >= buffer_len || len < 0 || len > buffer_len ||
+      offset > buffer_len - len) {
+    jniThrowException(env, "java/lang/IndexOutOfBoundsException", "");
+    return -1;
+  }
+
+  ScopedByteArrayRW byte_array(env, java_buffer);
+  if (byte_array.get() == nullptr) {
+    return -1;
+  }
+
+  Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+  ssize_t res = asset->read(byte_array.get() + offset, len);
+  if (res < 0) {
+    jniThrowException(env, "java/io/IOException", "");
+    return -1;
+  }
+  return res > 0 ? static_cast<jint>(res) : -1;
+}
+
+static jlong NativeAssetSeek(JNIEnv* env, jclass /*clazz*/, jlong asset_ptr, jlong offset,
+                             jint whence) {
+  Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+  return static_cast<jlong>(asset->seek(
+      static_cast<off64_t>(offset), (whence > 0 ? SEEK_END : (whence < 0 ? SEEK_SET : SEEK_CUR))));
+}
+
+static jlong NativeAssetGetLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+  Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+  return static_cast<jlong>(asset->getLength());
+}
+
+static jlong NativeAssetGetRemainingLength(JNIEnv* /*env*/, jclass /*clazz*/, jlong asset_ptr) {
+  Asset* asset = reinterpret_cast<Asset*>(asset_ptr);
+  return static_cast<jlong>(asset->getRemainingLength());
+}
+
+// ----------------------------------------------------------------------------
+
+// JNI registration.
+static const JNINativeMethod gAssetManagerMethods[] = {
+    // AssetManager setup methods.
+    {"nativeCreate", "()J", (void*)NativeCreate},
+    {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+    {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
+    {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIII)V",
+     (void*)NativeSetConfiguration},
+    {"nativeGetAssignedPackageIdentifiers", "(J)Landroid/util/SparseArray;",
+     (void*)NativeGetAssignedPackageIdentifiers},
+
+    // AssetManager file methods.
+    {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
+    {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
+    {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+     (void*)NativeOpenAssetFd},
+    {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
+    {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+     (void*)NativeOpenNonAssetFd},
+    {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+
+    // AssetManager resource methods.
+    {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
+    {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
+     (void*)NativeGetResourceBagValue},
+    {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
+    {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
+     (void*)NativeGetResourceStringArray},
+    {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
+    {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
+    {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
+    {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
+
+    // AssetManager resource name/ID methods.
+    {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+     (void*)NativeGetResourceIdentifier},
+    {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
+    {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName},
+    {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
+    {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
+    {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
+    {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
+     (void*)NativeGetSizeConfigurations},
+
+    // Style attribute related methods.
+    {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
+    {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
+    {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
+
+    // Theme related methods.
+    {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
+    {"nativeThemeDestroy", "(J)V", (void*)NativeThemeDestroy},
+    {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
+    {"nativeThemeCopy", "(JJ)V", (void*)NativeThemeCopy},
+    {"nativeThemeClear", "(J)V", (void*)NativeThemeClear},
+    {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
+     (void*)NativeThemeGetAttributeValue},
+    {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
+    {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations},
+
+    // AssetInputStream methods.
+    {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
+    {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
+    {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
+    {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
+    {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
+    {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
+
+    // System/idmap related methods.
+    {"nativeVerifySystemIdmaps", "()V", (void*)NativeVerifySystemIdmaps},
+
+    // Global management/debug methods.
+    {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
+    {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
+    {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
+};
+
+int register_android_content_AssetManager(JNIEnv* env) {
+  jclass apk_assets_class = FindClassOrDie(env, "android/content/res/ApkAssets");
+  gApkAssetsFields.native_ptr = GetFieldIDOrDie(env, apk_assets_class, "mNativePtr", "J");
+
+  jclass typedValue = FindClassOrDie(env, "android/util/TypedValue");
+  gTypedValueOffsets.mType = GetFieldIDOrDie(env, typedValue, "type", "I");
+  gTypedValueOffsets.mData = GetFieldIDOrDie(env, typedValue, "data", "I");
+  gTypedValueOffsets.mString =
+      GetFieldIDOrDie(env, typedValue, "string", "Ljava/lang/CharSequence;");
+  gTypedValueOffsets.mAssetCookie = GetFieldIDOrDie(env, typedValue, "assetCookie", "I");
+  gTypedValueOffsets.mResourceId = GetFieldIDOrDie(env, typedValue, "resourceId", "I");
+  gTypedValueOffsets.mChangingConfigurations =
+      GetFieldIDOrDie(env, typedValue, "changingConfigurations", "I");
+  gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I");
+
+  jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor");
+  gAssetFileDescriptorOffsets.mFd =
+      GetFieldIDOrDie(env, assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;");
+  gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J");
+  gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J");
+
+  jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager");
+  gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J");
+
+  jclass stringClass = FindClassOrDie(env, "java/lang/String");
+  g_stringClass = MakeGlobalRefOrDie(env, stringClass);
+
+  jclass sparseArrayClass = FindClassOrDie(env, "android/util/SparseArray");
+  gSparseArrayOffsets.classObject = MakeGlobalRefOrDie(env, sparseArrayClass);
+  gSparseArrayOffsets.constructor =
+      GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "<init>", "()V");
+  gSparseArrayOffsets.put =
+      GetMethodIDOrDie(env, gSparseArrayOffsets.classObject, "put", "(ILjava/lang/Object;)V");
+
+  jclass configurationClass = FindClassOrDie(env, "android/content/res/Configuration");
+  gConfigurationOffsets.classObject = MakeGlobalRefOrDie(env, configurationClass);
+  gConfigurationOffsets.constructor = GetMethodIDOrDie(env, configurationClass, "<init>", "()V");
+  gConfigurationOffsets.mSmallestScreenWidthDpOffset =
+      GetFieldIDOrDie(env, configurationClass, "smallestScreenWidthDp", "I");
+  gConfigurationOffsets.mScreenWidthDpOffset =
+      GetFieldIDOrDie(env, configurationClass, "screenWidthDp", "I");
+  gConfigurationOffsets.mScreenHeightDpOffset =
+      GetFieldIDOrDie(env, configurationClass, "screenHeightDp", "I");
+
+  return RegisterMethodsOrDie(env, "android/content/res/AssetManager", gAssetManagerMethods,
+                              NELEM(gAssetManagerMethods));
 }
 
 }; // namespace android
diff --git a/core/jni/include/android_runtime/android_util_AssetManager.h b/core/jni/include/android_runtime/android_util_AssetManager.h
index 8dd9337..2c1e357 100644
--- a/core/jni/include/android_runtime/android_util_AssetManager.h
+++ b/core/jni/include/android_runtime/android_util_AssetManager.h
@@ -14,17 +14,20 @@
  * limitations under the License.
  */
 
-#ifndef android_util_AssetManager_H
-#define android_util_AssetManager_H
+#ifndef ANDROID_RUNTIME_ASSETMANAGER_H
+#define ANDROID_RUNTIME_ASSETMANAGER_H
 
-#include <androidfw/AssetManager.h>
+#include "androidfw/AssetManager2.h"
+#include "androidfw/MutexGuard.h"
 
 #include "jni.h"
 
 namespace android {
 
-extern AssetManager* assetManagerForJavaObject(JNIEnv* env, jobject assetMgr);
+extern AAssetManager* NdkAssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager);
+extern Guarded<AssetManager2>* AssetManagerForJavaObject(JNIEnv* env, jobject jassetmanager);
+extern Guarded<AssetManager2>* AssetManagerForNdkAssetManager(AAssetManager* assetmanager);
 
-}
+}  // namespace android
 
-#endif
+#endif  // ANDROID_RUNTIME_ASSETMANAGER_H
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 0b0ed83..698f394 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -204,7 +204,10 @@
         (section).args = "power --proto"
     ];
 
-    optional android.service.print.PrintServiceDumpProto print = 3010;
+    optional android.service.print.PrintServiceDumpProto print = 3010 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "print --proto"
+    ];
 
     optional android.service.procstats.ProcessStatsServiceDumpProto procstats = 3011 [
         (section).type = SECTION_DUMPSYS,
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 449e546..c11058a 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -295,6 +295,7 @@
   optional bool animating_exit = 14;
   repeated WindowStateProto child_windows = 15;
   optional .android.graphics.RectProto surface_position = 16;
+  optional .android.graphics.RectProto shown_position = 17;
   optional int32 requested_width = 18;
   optional int32 requested_height = 19;
   optional int32 view_visibility = 20;
diff --git a/core/proto/android/service/print.proto b/core/proto/android/service/print.proto
index c2be7f1..f783b86 100644
--- a/core/proto/android/service/print.proto
+++ b/core/proto/android/service/print.proto
@@ -21,13 +21,18 @@
 option java_outer_classname = "PrintServiceProto";
 
 import "frameworks/base/core/proto/android/content/component_name.proto";
+import "frameworks/base/libs/incident/proto/android/privacy.proto";
 
 message PrintServiceDumpProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Each user has a separate printer state
     repeated PrintUserStateProto userStates = 1;
 }
 
 message PrintUserStateProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Should be 0, 10, 11, 12, etc. where 0 is the owner.
     optional int32 user_id = 1;
 
@@ -51,6 +56,8 @@
 }
 
 message PrintSpoolerStateProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Is the print spooler destroyed?
     optional bool is_destroyed = 1;
 
@@ -62,6 +69,8 @@
 }
 
 message PrintSpoolerInternalStateProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Print jobs
     repeated PrintJobInfoProto print_jobs = 1;
 
@@ -73,6 +82,8 @@
 }
 
 message PrinterCapabilitiesProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Minimum margins of the printer
     optional MarginsProto min_margins = 1;
 
@@ -90,6 +101,8 @@
 }
 
 message PrinterInfoProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // The id of the printer
     optional PrinterIdProto id = 1;
 
@@ -120,6 +133,8 @@
 }
 
 message PrinterDiscoverySessionProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Is this session destroyed?
     optional bool is_destroyed = 1;
 
@@ -140,6 +155,8 @@
 }
 
 message InstalledPrintServiceProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Component name of the service
     optional android.content.ComponentNameProto component_name = 1;
 
@@ -154,14 +171,18 @@
 }
 
 message PrinterIdProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Component name of the service that reported the printer
     optional android.content.ComponentNameProto service_name = 1;
 
     // Local id of the printer
-    optional string local_id = 2;
+    optional string local_id = 2 [ (android.privacy).dest = DEST_EXPLICIT ];
 }
 
 message ActivePrintServiceProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Component name of the service
     optional android.content.ComponentNameProto component_name = 1;
 
@@ -185,6 +206,8 @@
 }
 
 message MediaSizeProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Id of this media size
     optional string id = 1;
 
@@ -199,6 +222,8 @@
 }
 
 message ResolutionProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Id of this resolution
     optional string id = 1;
 
@@ -213,6 +238,8 @@
 }
 
 message MarginsProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Space at the top
     optional int32 top_mils = 1;
 
@@ -227,6 +254,8 @@
 }
 
 message PrintAttributesProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Media to use
     optional ResolutionProto media_size = 1;
 
@@ -270,8 +299,10 @@
 }
 
 message PrintDocumentInfoProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Name of the document to print
-    optional string name = 1;
+    optional string name = 1 [ (android.privacy).dest = DEST_EXPLICIT ];
 
     // Number of pages in the doc
     optional int32 page_count = 2;
@@ -284,6 +315,8 @@
 }
 
 message PageRangeProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Start of the range
     optional int32 start = 1;
 
@@ -292,8 +325,10 @@
 }
 
 message PrintJobInfoProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // Label of the job
-    optional string label = 1;
+    optional string label = 1 [ (android.privacy).dest = DEST_EXPLICIT ];
 
     // Id of the job
     optional string print_job_id = 2;
@@ -359,6 +394,8 @@
 }
 
 message CachedPrintJobProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
     // The id of the app the job belongs to
     optional int32 app_id = 1;
 
diff --git a/core/res/res/layout/notification_template_messaging_group.xml b/core/res/res/layout/notification_template_messaging_group.xml
index 4ac308a..bd1030e 100644
--- a/core/res/res/layout/notification_template_messaging_group.xml
+++ b/core/res/res/layout/notification_template_messaging_group.xml
@@ -28,9 +28,9 @@
         android:scaleType="centerCrop"
         android:importantForAccessibility="no" />
     <com.android.internal.widget.RemeasuringLinearLayout
-        android:id="@+id/message_group_and_sender_container"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_weight="1"
         android:orientation="vertical">
         <com.android.internal.widget.ImageFloatingTextView
             android:id="@+id/message_name"
@@ -44,4 +44,10 @@
             android:spacing="2dp"
             android:layout_weight="1"/>
     </com.android.internal.widget.RemeasuringLinearLayout>
+    <FrameLayout
+        android:id="@+id/messaging_group_icon_container"
+        android:layout_width="@dimen/messaging_avatar_size"
+        android:layout_height="@dimen/messaging_avatar_size"
+        android:layout_marginStart="12dp"
+        android:visibility="gone"/>
 </com.android.internal.widget.MessagingGroup>
diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_image_message.xml
similarity index 71%
copy from core/res/res/layout/notification_template_messaging_message.xml
copy to core/res/res/layout/notification_template_messaging_image_message.xml
index ab6466c..6ca4dcd 100644
--- a/core/res/res/layout/notification_template_messaging_message.xml
+++ b/core/res/res/layout/notification_template_messaging_image_message.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2017 The Android Open Source Project
+  ~ Copyright (C) 2018 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -14,8 +14,11 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<com.android.internal.widget.MessagingMessage
+<com.android.internal.widget.MessagingImageMessage
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/message_text"
-    style="@style/Widget.Material.Notification.MessagingText"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginTop="@dimen/messaging_image_extra_spacing"
+    android:scaleType="fitStart"
 />
diff --git a/core/res/res/layout/notification_template_messaging_message.xml b/core/res/res/layout/notification_template_messaging_text_message.xml
similarity index 94%
rename from core/res/res/layout/notification_template_messaging_message.xml
rename to core/res/res/layout/notification_template_messaging_text_message.xml
index ab6466c..e728e69 100644
--- a/core/res/res/layout/notification_template_messaging_message.xml
+++ b/core/res/res/layout/notification_template_messaging_text_message.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License
   -->
-<com.android.internal.widget.MessagingMessage
+<com.android.internal.widget.MessagingTextMessage
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:id="@+id/message_text"
     style="@style/Widget.Material.Notification.MessagingText"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e610efd..0411c6e 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -262,6 +262,18 @@
     <!-- The spacing between messages in Notification.MessagingStyle -->
     <dimen name="notification_messaging_spacing">6dp</dimen>
 
+    <!-- The rounding for messaging images -->
+    <dimen name="messaging_image_rounding">4dp</dimen>
+
+    <!-- The minimum size for any image in messaging style in order to be displayed -->
+    <dimen name="messaging_image_min_size">44dp</dimen>
+
+    <!-- The maximum size for any image in messaging style in order to be displayed -->
+    <dimen name="messaging_image_max_height">136dp</dimen>
+
+    <!-- Extra spacing before and after images in messaging style -->
+    <dimen name="messaging_image_extra_spacing">8dp</dimen>
+
     <!-- Preferred width and height of the search view. -->
     <dimen name="search_view_preferred_width">320dip</dimen>
     <dimen name="search_view_preferred_height">48dip</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 29b09b5..f7b6f06a 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3168,7 +3168,8 @@
   <java-symbol type="dimen" name="chooser_service_spacing" />
   <java-symbol type="bool" name="config_showSysuiShutdown" />
 
-  <java-symbol type="layout" name="notification_template_messaging_message" />
+  <java-symbol type="layout" name="notification_template_messaging_text_message" />
+  <java-symbol type="layout" name="notification_template_messaging_image_message" />
   <java-symbol type="layout" name="notification_template_messaging_group" />
   <java-symbol type="id" name="message_text" />
   <java-symbol type="id" name="message_name" />
@@ -3183,6 +3184,11 @@
   <java-symbol type="id" name="clip_children_tag" />
   <java-symbol type="drawable" name="ic_reply_notification_large" />
   <java-symbol type="dimen" name="messaging_avatar_size" />
+  <java-symbol type="dimen" name="messaging_image_rounding" />
+  <java-symbol type="dimen" name="messaging_image_min_size" />
+  <java-symbol type="dimen" name="messaging_image_max_height" />
+  <java-symbol type="dimen" name="messaging_image_extra_spacing" />
+  <java-symbol type="id" name="messaging_group_icon_container" />
 
   <java-symbol type="integer" name="config_stableDeviceDisplayWidth" />
   <java-symbol type="integer" name="config_stableDeviceDisplayHeight" />
diff --git a/core/tests/coretests/res/layout/activity_text_view.xml b/core/tests/coretests/res/layout/activity_text_view.xml
index dca1656..d5be87d 100644
--- a/core/tests/coretests/res/layout/activity_text_view.xml
+++ b/core/tests/coretests/res/layout/activity_text_view.xml
@@ -24,6 +24,9 @@
             android:id="@+id/textview"
             android:layout_width="match_parent"
             android:layout_height="wrap_content" />
+    <Space
+        android:layout_width="1dp"
+        android:layout_height="60dp"/>
 
     <TextView
         android:id="@+id/nonselectable_textview"
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index da86c9f..05c12ae 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -17,9 +17,11 @@
 package android.provider;
 
 import static com.google.android.collect.Sets.newHashSet;
+
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.is;
+
 import static java.lang.reflect.Modifier.isFinal;
 import static java.lang.reflect.Modifier.isPublic;
 import static java.lang.reflect.Modifier.isStatic;
@@ -194,6 +196,7 @@
                     Settings.Global.DEVICE_PROVISIONED,
                     Settings.Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED,
                     Settings.Global.DISK_FREE_CHANGE_REPORTING_THRESHOLD,
+                    Settings.Global.DISPLAY_PANEL_LPM,
                     Settings.Global.DISPLAY_SCALING_FORCE,
                     Settings.Global.DISPLAY_SIZE_FORCED,
                     Settings.Global.DNS_RESOLVER_MAX_SAMPLES,
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityTest.java b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
index 69e5670..7f4f9f7 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityTest.java
@@ -66,6 +66,7 @@
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
+import android.support.test.uiautomator.UiDevice;
 import android.test.suitebuilder.annotation.Suppress;
 import android.text.InputType;
 import android.text.Selection;
@@ -311,15 +312,69 @@
 
     @Test
     public void testToolbarAppearsAfterLinkClicked() throws Throwable {
-        runToolbarAppearsAfterLinkClickedTest(R.id.textview);
+        TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview);
+        int position = (textLink.getStart() + textLink.getEnd()) / 2;
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(position));
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
+
     }
 
     @Test
     public void testToolbarAppearsAfterLinkClickedNonselectable() throws Throwable {
-        runToolbarAppearsAfterLinkClickedTest(R.id.nonselectable_textview);
+        TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
+        int position = (textLink.getStart() + textLink.getEnd()) / 2;
+        onView(withId(R.id.nonselectable_textview)).perform(clickOnTextAtIndex(position));
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
     }
 
-    private void runToolbarAppearsAfterLinkClickedTest(int id) throws Throwable {
+    @Test
+    public void testSelectionRemovedWhenNonselectableTextLosesFocus() throws Throwable {
+        // Add a link to both selectable and nonselectable TextViews:
+        TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.textview);
+        int selectablePosition = (textLink.getStart() + textLink.getEnd()) / 2;
+        textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
+        int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2;
+        TextView selectableTextView = mActivity.findViewById(R.id.textview);
+        TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview);
+
+        onView(withId(R.id.nonselectable_textview))
+                .perform(clickOnTextAtIndex(nonselectablePosition));
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
+        assertTrue(nonselectableTextView.hasSelection());
+
+        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(selectablePosition));
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
+
+        assertTrue(selectableTextView.hasSelection());
+        assertFalse(nonselectableTextView.hasSelection());
+    }
+
+    @Test
+    public void testSelectionRemovedFromNonselectableTextWhenWindowLosesFocus() throws Throwable {
+        TextLinks.TextLink textLink = addLinkifiedTextToTextView(R.id.nonselectable_textview);
+        int nonselectablePosition = (textLink.getStart() + textLink.getEnd()) / 2;
+        TextView nonselectableTextView = mActivity.findViewById(R.id.nonselectable_textview);
+
+        onView(withId(R.id.nonselectable_textview))
+                .perform(clickOnTextAtIndex(nonselectablePosition));
+        sleepForFloatingToolbarPopup();
+        assertFloatingToolbarIsDisplayed();
+        assertTrue(nonselectableTextView.hasSelection());
+
+        UiDevice device = UiDevice.getInstance(mInstrumentation);
+        device.openNotification();
+        Thread.sleep(2000);
+        device.pressBack();
+        Thread.sleep(2000);
+
+        assertFalse(nonselectableTextView.hasSelection());
+    }
+
+    private TextLinks.TextLink addLinkifiedTextToTextView(int id) throws Throwable {
         TextView textView = mActivity.findViewById(id);
         useSystemDefaultTextClassifier();
         TextClassificationManager textClassificationManager =
@@ -338,11 +393,7 @@
         // Wait for the UI thread to refresh
         Thread.sleep(1000);
 
-        TextLinks.TextLink textLink = links.getLinks().iterator().next();
-        int position = (textLink.getStart() + textLink.getEnd()) / 2;
-        onView(withId(id)).perform(clickOnTextAtIndex(position));
-        sleepForFloatingToolbarPopup();
-        assertFloatingToolbarIsDisplayed();
+        return links.getLinks().iterator().next();
     }
 
     @Test
diff --git a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
index 3919fdd..41082b7 100644
--- a/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
+++ b/core/tests/coretests/src/com/android/internal/widget/MessagingLinearLayoutTest.java
@@ -127,7 +127,7 @@
         assertEquals(355, mView.getMeasuredHeight());;
     }
 
-    private class FakeImageFloatingTextView extends MessagingMessage {
+    private class FakeImageFloatingTextView extends MessagingTextMessage {
 
         public static final int LINE_HEIGHT = 50;
         private final int mNumLines;
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index acefead..3cca47b 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -25,7 +25,7 @@
 import android.annotation.RawRes;
 import android.content.ContentResolver;
 import android.content.res.AssetFileDescriptor;
-import android.content.res.AssetManager;
+import android.content.res.AssetManager.AssetInputStream;
 import android.content.res.Resources;
 import android.graphics.drawable.AnimatedImageDrawable;
 import android.graphics.drawable.Drawable;
@@ -263,6 +263,63 @@
         }
     }
 
+    /**
+     * Takes ownership of the AssetInputStream.
+     *
+     * @hide
+     */
+    public static class AssetInputStreamSource extends Source {
+        public AssetInputStreamSource(@NonNull AssetInputStream ais,
+                @NonNull Resources res, @NonNull TypedValue value) {
+            mAssetInputStream = ais;
+            mResources = res;
+
+            if (value.density == TypedValue.DENSITY_DEFAULT) {
+                mDensity = DisplayMetrics.DENSITY_DEFAULT;
+            } else if (value.density != TypedValue.DENSITY_NONE) {
+                mDensity = value.density;
+            } else {
+                mDensity = Bitmap.DENSITY_NONE;
+            }
+        }
+
+        private AssetInputStream mAssetInputStream;
+        private final Resources  mResources;
+        private final int        mDensity;
+
+        @Override
+        public Resources getResources() { return mResources; }
+
+        @Override
+        public int getDensity() {
+            return mDensity;
+        }
+
+        @Override
+        public ImageDecoder createImageDecoder() throws IOException {
+            ImageDecoder decoder = null;
+            synchronized (this) {
+                if (mAssetInputStream == null) {
+                    throw new IOException("Cannot reuse AssetInputStreamSource");
+                }
+                AssetInputStream ais = mAssetInputStream;
+                mAssetInputStream = null;
+                try {
+                    long asset = ais.getNativeAsset();
+                    decoder = nCreate(asset);
+                } finally {
+                    if (decoder == null) {
+                        IoUtils.closeQuietly(ais);
+                    } else {
+                        decoder.mInputStream = ais;
+                        decoder.mOwnsInputStream = true;
+                    }
+                }
+                return decoder;
+            }
+        }
+    }
+
     private static class ResourceSource extends Source {
         ResourceSource(@NonNull Resources res, int resId) {
             mResources = res;
@@ -296,11 +353,7 @@
                     mResDensity = value.density;
                 }
 
-                if (!(is instanceof AssetManager.AssetInputStream)) {
-                    // This should never happen.
-                    throw new RuntimeException("Resource is not an asset?");
-                }
-                long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
+                long asset = ((AssetInputStream) is).getNativeAsset();
                 decoder = nCreate(asset);
             } finally {
                 if (decoder == null) {
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 251b2e7..70d5216 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -145,6 +145,7 @@
         "tests/TypeWrappers_test.cpp",
         "tests/ZipUtils_test.cpp",
     ],
+    static_libs: ["libgmock"],
     target: {
         android: {
             srcs: [
@@ -171,6 +172,7 @@
 
         // Actual benchmarks.
         "tests/AssetManager2_bench.cpp",
+        "tests/AttributeResolution_bench.cpp",
         "tests/SparseEntry_bench.cpp",
         "tests/Theme_bench.cpp",
     ],
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 415d3e3..a8c916b 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -36,6 +36,31 @@
 
 namespace android {
 
+struct FindEntryResult {
+  // A pointer to the resource table entry for this resource.
+  // If the size of the entry is > sizeof(ResTable_entry), it can be cast to
+  // a ResTable_map_entry and processed as a bag/map.
+  const ResTable_entry* entry;
+
+  // The configuration for which the resulting entry was defined. This is already swapped to host
+  // endianness.
+  ResTable_config config;
+
+  // The bitmask of configuration axis with which the resource value varies.
+  uint32_t type_flags;
+
+  // The dynamic package ID map for the package from which this resource came from.
+  const DynamicRefTable* dynamic_ref_table;
+
+  // The string pool reference to the type's name. This uses a different string pool than
+  // the global string pool, but this is hidden from the caller.
+  StringPoolRef type_string_ref;
+
+  // The string pool reference to the entry's name. This uses a different string pool than
+  // the global string pool, but this is hidden from the caller.
+  StringPoolRef entry_string_ref;
+};
+
 AssetManager2::AssetManager2() {
   memset(&configuration_, 0, sizeof(configuration_));
 }
@@ -44,6 +69,7 @@
                                  bool invalidate_caches) {
   apk_assets_ = apk_assets;
   BuildDynamicRefTable();
+  RebuildFilterList();
   if (invalidate_caches) {
     InvalidateCaches(static_cast<uint32_t>(-1));
   }
@@ -74,12 +100,14 @@
       if (idx == 0xff) {
         package_ids_[package_id] = idx = static_cast<uint8_t>(package_groups_.size());
         package_groups_.push_back({});
-        package_groups_.back().dynamic_ref_table.mAssignedPackageId = package_id;
+        DynamicRefTable& ref_table = package_groups_.back().dynamic_ref_table;
+        ref_table.mAssignedPackageId = package_id;
+        ref_table.mAppAsLib = package->IsDynamic() && package->GetPackageId() == 0x7f;
       }
       PackageGroup* package_group = &package_groups_[idx];
 
       // Add the package and to the set of packages with the same ID.
-      package_group->packages_.push_back(package.get());
+      package_group->packages_.push_back(ConfiguredPackage{package.get(), {}});
       package_group->cookies_.push_back(static_cast<ApkAssetsCookie>(i));
 
       // Add the package name -> build time ID mappings.
@@ -94,7 +122,7 @@
   // Now assign the runtime IDs so that we have a build-time to runtime ID map.
   const auto package_groups_end = package_groups_.end();
   for (auto iter = package_groups_.begin(); iter != package_groups_end; ++iter) {
-    const std::string& package_name = iter->packages_[0]->GetPackageName();
+    const std::string& package_name = iter->packages_[0].loaded_package_->GetPackageName();
     for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
       iter2->dynamic_ref_table.addMapping(String16(package_name.c_str(), package_name.size()),
                                           iter->dynamic_ref_table.mAssignedPackageId);
@@ -105,20 +133,33 @@
 void AssetManager2::DumpToLog() const {
   base::ScopedLogSeverity _log(base::INFO);
 
+  LOG(INFO) << base::StringPrintf("AssetManager2(this=%p)", this);
+
   std::string list;
+  for (const auto& apk_assets : apk_assets_) {
+    base::StringAppendF(&list, "%s,", apk_assets->GetPath().c_str());
+  }
+  LOG(INFO) << "ApkAssets: " << list;
+
+  list = "";
   for (size_t i = 0; i < package_ids_.size(); i++) {
     if (package_ids_[i] != 0xff) {
-      base::StringAppendF(&list, "%02x -> %d, ", (int) i, package_ids_[i]);
+      base::StringAppendF(&list, "%02x -> %d, ", (int)i, package_ids_[i]);
     }
   }
   LOG(INFO) << "Package ID map: " << list;
 
   for (const auto& package_group: package_groups_) {
-      list = "";
-      for (const auto& package : package_group.packages_) {
-        base::StringAppendF(&list, "%s(%02x), ", package->GetPackageName().c_str(), package->GetPackageId());
-      }
-      LOG(INFO) << base::StringPrintf("PG (%02x): ", package_group.dynamic_ref_table.mAssignedPackageId) << list;
+    list = "";
+    for (const auto& package : package_group.packages_) {
+      const LoadedPackage* loaded_package = package.loaded_package_;
+      base::StringAppendF(&list, "%s(%02x%s), ", loaded_package->GetPackageName().c_str(),
+                          loaded_package->GetPackageId(),
+                          (loaded_package->IsDynamic() ? " dynamic" : ""));
+    }
+    LOG(INFO) << base::StringPrintf("PG (%02x): ",
+                                    package_group.dynamic_ref_table.mAssignedPackageId)
+              << list;
   }
 }
 
@@ -157,52 +198,54 @@
   configuration_ = configuration;
 
   if (diff) {
+    RebuildFilterList();
     InvalidateCaches(static_cast<uint32_t>(diff));
   }
 }
 
 std::set<ResTable_config> AssetManager2::GetResourceConfigurations(bool exclude_system,
-                                                                   bool exclude_mipmap) {
+                                                                   bool exclude_mipmap) const {
   ATRACE_CALL();
   std::set<ResTable_config> configurations;
   for (const PackageGroup& package_group : package_groups_) {
-    for (const LoadedPackage* package : package_group.packages_) {
-      if (exclude_system && package->IsSystem()) {
+    for (const ConfiguredPackage& package : package_group.packages_) {
+      if (exclude_system && package.loaded_package_->IsSystem()) {
         continue;
       }
-      package->CollectConfigurations(exclude_mipmap, &configurations);
+      package.loaded_package_->CollectConfigurations(exclude_mipmap, &configurations);
     }
   }
   return configurations;
 }
 
 std::set<std::string> AssetManager2::GetResourceLocales(bool exclude_system,
-                                                        bool merge_equivalent_languages) {
+                                                        bool merge_equivalent_languages) const {
   ATRACE_CALL();
   std::set<std::string> locales;
   for (const PackageGroup& package_group : package_groups_) {
-    for (const LoadedPackage* package : package_group.packages_) {
-      if (exclude_system && package->IsSystem()) {
+    for (const ConfiguredPackage& package : package_group.packages_) {
+      if (exclude_system && package.loaded_package_->IsSystem()) {
         continue;
       }
-      package->CollectLocales(merge_equivalent_languages, &locales);
+      package.loaded_package_->CollectLocales(merge_equivalent_languages, &locales);
     }
   }
   return locales;
 }
 
-std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, Asset::AccessMode mode) {
+std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename,
+                                           Asset::AccessMode mode) const {
   const std::string new_path = "assets/" + filename;
   return OpenNonAsset(new_path, mode);
 }
 
 std::unique_ptr<Asset> AssetManager2::Open(const std::string& filename, ApkAssetsCookie cookie,
-                                           Asset::AccessMode mode) {
+                                           Asset::AccessMode mode) const {
   const std::string new_path = "assets/" + filename;
   return OpenNonAsset(new_path, cookie, mode);
 }
 
-std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) {
+std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) const {
   ATRACE_CALL();
 
   std::string full_path = "assets/" + dirname;
@@ -236,7 +279,7 @@
 // is inconsistent for split APKs.
 std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
                                                    Asset::AccessMode mode,
-                                                   ApkAssetsCookie* out_cookie) {
+                                                   ApkAssetsCookie* out_cookie) const {
   ATRACE_CALL();
   for (int32_t i = apk_assets_.size() - 1; i >= 0; i--) {
     std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
@@ -255,7 +298,8 @@
 }
 
 std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
-                                                   ApkAssetsCookie cookie, Asset::AccessMode mode) {
+                                                   ApkAssetsCookie cookie,
+                                                   Asset::AccessMode mode) const {
   ATRACE_CALL();
   if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
     return {};
@@ -264,14 +308,13 @@
 }
 
 ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
-                                         bool stop_at_first_match, FindEntryResult* out_entry) {
-  ATRACE_CALL();
-
+                                         bool /*stop_at_first_match*/,
+                                         FindEntryResult* out_entry) const {
   // Might use this if density_override != 0.
   ResTable_config density_override_config;
 
   // Select our configuration or generate a density override configuration.
-  ResTable_config* desired_config = &configuration_;
+  const ResTable_config* desired_config = &configuration_;
   if (density_override != 0 && density_override != configuration_.density) {
     density_override_config = configuration_;
     density_override_config.density = density_override;
@@ -285,53 +328,135 @@
 
   const uint32_t package_id = get_package_id(resid);
   const uint8_t type_idx = get_type_id(resid) - 1;
-  const uint16_t entry_id = get_entry_id(resid);
+  const uint16_t entry_idx = get_entry_id(resid);
 
-  const uint8_t idx = package_ids_[package_id];
-  if (idx == 0xff) {
+  const uint8_t package_idx = package_ids_[package_id];
+  if (package_idx == 0xff) {
     LOG(ERROR) << base::StringPrintf("No package ID %02x found for ID 0x%08x.", package_id, resid);
     return kInvalidCookie;
   }
 
-  FindEntryResult best_entry;
-  ApkAssetsCookie best_cookie = kInvalidCookie;
-  uint32_t cumulated_flags = 0u;
-
-  const PackageGroup& package_group = package_groups_[idx];
+  const PackageGroup& package_group = package_groups_[package_idx];
   const size_t package_count = package_group.packages_.size();
-  FindEntryResult current_entry;
-  for (size_t i = 0; i < package_count; i++) {
-    const LoadedPackage* loaded_package = package_group.packages_[i];
-    if (!loaded_package->FindEntry(type_idx, entry_id, *desired_config, &current_entry)) {
+
+  ApkAssetsCookie best_cookie = kInvalidCookie;
+  const LoadedPackage* best_package = nullptr;
+  const ResTable_type* best_type = nullptr;
+  const ResTable_config* best_config = nullptr;
+  ResTable_config best_config_copy;
+  uint32_t best_offset = 0u;
+  uint32_t type_flags = 0u;
+
+  // If desired_config is the same as the set configuration, then we can use our filtered list
+  // and we don't need to match the configurations, since they already matched.
+  const bool use_fast_path = desired_config == &configuration_;
+
+  for (size_t pi = 0; pi < package_count; pi++) {
+    const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
+    const LoadedPackage* loaded_package = loaded_package_impl.loaded_package_;
+    ApkAssetsCookie cookie = package_group.cookies_[pi];
+
+    // If the type IDs are offset in this package, we need to take that into account when searching
+    // for a type.
+    const TypeSpec* type_spec = loaded_package->GetTypeSpecByTypeIndex(type_idx);
+    if (UNLIKELY(type_spec == nullptr)) {
       continue;
     }
 
-    cumulated_flags |= current_entry.type_flags;
+    uint16_t local_entry_idx = entry_idx;
 
-    const ResTable_config* current_config = current_entry.config;
-    const ResTable_config* best_config = best_entry.config;
-    if (best_cookie == kInvalidCookie ||
-        current_config->isBetterThan(*best_config, desired_config) ||
-        (loaded_package->IsOverlay() && current_config->compare(*best_config) == 0)) {
-      best_entry = current_entry;
-      best_cookie = package_group.cookies_[i];
-      if (stop_at_first_match) {
-        break;
+    // If there is an IDMAP supplied with this package, translate the entry ID.
+    if (type_spec->idmap_entries != nullptr) {
+      if (!LoadedIdmap::Lookup(type_spec->idmap_entries, local_entry_idx, &local_entry_idx)) {
+        // There is no mapping, so the resource is not meant to be in this overlay package.
+        continue;
+      }
+    }
+
+    type_flags |= type_spec->GetFlagsForEntryIndex(local_entry_idx);
+
+    // If the package is an overlay, then even configurations that are the same MUST be chosen.
+    const bool package_is_overlay = loaded_package->IsOverlay();
+
+    const FilteredConfigGroup& filtered_group = loaded_package_impl.filtered_configs_[type_idx];
+    if (use_fast_path) {
+      const std::vector<ResTable_config>& candidate_configs = filtered_group.configurations;
+      const size_t type_count = candidate_configs.size();
+      for (uint32_t i = 0; i < type_count; i++) {
+        const ResTable_config& this_config = candidate_configs[i];
+
+        // We can skip calling ResTable_config::match() because we know that all candidate
+        // configurations that do NOT match have been filtered-out.
+        if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
+            (package_is_overlay && this_config.compare(*best_config) == 0)) {
+          // The configuration matches and is better than the previous selection.
+          // Find the entry value if it exists for this configuration.
+          const ResTable_type* type_chunk = filtered_group.types[i];
+          const uint32_t offset = LoadedPackage::GetEntryOffset(type_chunk, local_entry_idx);
+          if (offset == ResTable_type::NO_ENTRY) {
+            continue;
+          }
+
+          best_cookie = cookie;
+          best_package = loaded_package;
+          best_type = type_chunk;
+          best_config = &this_config;
+          best_offset = offset;
+        }
+      }
+    } else {
+      // This is the slower path, which doesn't use the filtered list of configurations.
+      // Here we must read the ResTable_config from the mmapped APK, convert it to host endianness
+      // and fill in any new fields that did not exist when the APK was compiled.
+      // Furthermore when selecting configurations we can't just record the pointer to the
+      // ResTable_config, we must copy it.
+      const auto iter_end = type_spec->types + type_spec->type_count;
+      for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+        ResTable_config this_config;
+        this_config.copyFromDtoH((*iter)->config);
+
+        if (this_config.match(*desired_config)) {
+          if ((best_config == nullptr || this_config.isBetterThan(*best_config, desired_config)) ||
+              (package_is_overlay && this_config.compare(*best_config) == 0)) {
+            // The configuration matches and is better than the previous selection.
+            // Find the entry value if it exists for this configuration.
+            const uint32_t offset = LoadedPackage::GetEntryOffset(*iter, local_entry_idx);
+            if (offset == ResTable_type::NO_ENTRY) {
+              continue;
+            }
+
+            best_cookie = cookie;
+            best_package = loaded_package;
+            best_type = *iter;
+            best_config_copy = this_config;
+            best_config = &best_config_copy;
+            best_offset = offset;
+          }
+        }
       }
     }
   }
 
-  if (best_cookie == kInvalidCookie) {
+  if (UNLIKELY(best_cookie == kInvalidCookie)) {
     return kInvalidCookie;
   }
 
-  *out_entry = best_entry;
+  const ResTable_entry* best_entry = LoadedPackage::GetEntryFromOffset(best_type, best_offset);
+  if (UNLIKELY(best_entry == nullptr)) {
+    return kInvalidCookie;
+  }
+
+  out_entry->entry = best_entry;
+  out_entry->config = *best_config;
+  out_entry->type_flags = type_flags;
+  out_entry->type_string_ref = StringPoolRef(best_package->GetTypeStringPool(), best_type->id - 1);
+  out_entry->entry_string_ref =
+      StringPoolRef(best_package->GetKeyStringPool(), best_entry->key.index);
   out_entry->dynamic_ref_table = &package_group.dynamic_ref_table;
-  out_entry->type_flags = cumulated_flags;
   return best_cookie;
 }
 
-bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) {
+bool AssetManager2::GetResourceName(uint32_t resid, ResourceName* out_name) const {
   ATRACE_CALL();
 
   FindEntryResult entry;
@@ -341,7 +466,8 @@
     return false;
   }
 
-  const LoadedPackage* package = apk_assets_[cookie]->GetLoadedArsc()->GetPackageForId(resid);
+  const LoadedPackage* package =
+      apk_assets_[cookie]->GetLoadedArsc()->GetPackageById(get_package_id(resid));
   if (package == nullptr) {
     return false;
   }
@@ -369,7 +495,7 @@
   return true;
 }
 
-bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) {
+bool AssetManager2::GetResourceFlags(uint32_t resid, uint32_t* out_flags) const {
   FindEntryResult entry;
   ApkAssetsCookie cookie =
       FindEntry(resid, 0u /* density_override */, false /* stop_at_first_match */, &entry);
@@ -383,7 +509,7 @@
 ApkAssetsCookie AssetManager2::GetResource(uint32_t resid, bool may_be_bag,
                                            uint16_t density_override, Res_value* out_value,
                                            ResTable_config* out_selected_config,
-                                           uint32_t* out_flags) {
+                                           uint32_t* out_flags) const {
   ATRACE_CALL();
 
   FindEntryResult entry;
@@ -402,7 +528,7 @@
     // Create a reference since we can't represent this complex type as a Res_value.
     out_value->dataType = Res_value::TYPE_REFERENCE;
     out_value->data = resid;
-    *out_selected_config = *entry.config;
+    *out_selected_config = entry.config;
     *out_flags = entry.type_flags;
     return cookie;
   }
@@ -414,7 +540,7 @@
   // Convert the package ID to the runtime assigned package ID.
   entry.dynamic_ref_table->lookupResourceValue(out_value);
 
-  *out_selected_config = *entry.config;
+  *out_selected_config = entry.config;
   *out_flags = entry.type_flags;
   return cookie;
 }
@@ -422,16 +548,14 @@
 ApkAssetsCookie AssetManager2::ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
                                                 ResTable_config* in_out_selected_config,
                                                 uint32_t* in_out_flags,
-                                                uint32_t* out_last_reference) {
+                                                uint32_t* out_last_reference) const {
   ATRACE_CALL();
   constexpr const int kMaxIterations = 20;
 
   for (size_t iteration = 0u; in_out_value->dataType == Res_value::TYPE_REFERENCE &&
                               in_out_value->data != 0u && iteration < kMaxIterations;
        iteration++) {
-    if (out_last_reference != nullptr) {
-      *out_last_reference = in_out_value->data;
-    }
+    *out_last_reference = in_out_value->data;
     uint32_t new_flags = 0u;
     cookie = GetResource(in_out_value->data, true /*may_be_bag*/, 0u /*density_override*/,
                          in_out_value, in_out_selected_config, &new_flags);
@@ -492,7 +616,8 @@
         // Attributes, arrays, etc don't have a resource id as the name. They specify
         // other data, which would be wrong to change via a lookup.
         if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) {
-          LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid);
+          LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key,
+                                           resid);
           return nullptr;
         }
       }
@@ -524,7 +649,8 @@
   const ResolvedBag* parent_bag = GetBag(parent_resid);
   if (parent_bag == nullptr) {
     // Failed to get the parent that should exist.
-    LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid, resid);
+    LOG(ERROR) << base::StringPrintf("Failed to find parent 0x%08x of bag 0x%08x.", parent_resid,
+                                     resid);
     return nullptr;
   }
 
@@ -543,7 +669,8 @@
     uint32_t child_key = dtohl(map_entry->name.ident);
     if (!is_internal_resid(child_key)) {
       if (entry.dynamic_ref_table->lookupResourceId(&child_key) != NO_ERROR) {
-        LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key, resid);
+        LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", child_key,
+                                         resid);
         return nullptr;
       }
     }
@@ -582,7 +709,8 @@
     uint32_t new_key = dtohl(map_entry->name.ident);
     if (!is_internal_resid(new_key)) {
       if (entry.dynamic_ref_table->lookupResourceId(&new_key) != NO_ERROR) {
-        LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key, resid);
+        LOG(ERROR) << base::StringPrintf("Failed to resolve key 0x%08x in bag 0x%08x.", new_key,
+                                         resid);
         return nullptr;
       }
     }
@@ -638,7 +766,7 @@
 
 uint32_t AssetManager2::GetResourceId(const std::string& resource_name,
                                       const std::string& fallback_type,
-                                      const std::string& fallback_package) {
+                                      const std::string& fallback_package) const {
   StringPiece package_name, type, entry;
   if (!ExtractResourceName(resource_name, &package_name, &type, &entry)) {
     return 0u;
@@ -670,7 +798,8 @@
   const static std::u16string kAttrPrivate16 = u"^attr-private";
 
   for (const PackageGroup& package_group : package_groups_) {
-    for (const LoadedPackage* package : package_group.packages_) {
+    for (const ConfiguredPackage& package_impl : package_group.packages_) {
+      const LoadedPackage* package = package_impl.loaded_package_;
       if (package_name != package->GetPackageName()) {
         // All packages in the same group are expected to have the same package name.
         break;
@@ -692,6 +821,32 @@
   return 0u;
 }
 
+void AssetManager2::RebuildFilterList() {
+  for (PackageGroup& group : package_groups_) {
+    for (ConfiguredPackage& impl : group.packages_) {
+      // Destroy it.
+      impl.filtered_configs_.~ByteBucketArray();
+
+      // Re-create it.
+      new (&impl.filtered_configs_) ByteBucketArray<FilteredConfigGroup>();
+
+      // Create the filters here.
+      impl.loaded_package_->ForEachTypeSpec([&](const TypeSpec* spec, uint8_t type_index) {
+        FilteredConfigGroup& group = impl.filtered_configs_.editItemAt(type_index);
+        const auto iter_end = spec->types + spec->type_count;
+        for (auto iter = spec->types; iter != iter_end; ++iter) {
+          ResTable_config this_config;
+          this_config.copyFromDtoH((*iter)->config);
+          if (this_config.match(configuration_)) {
+            group.configurations.push_back(this_config);
+            group.types.push_back(*iter);
+          }
+        }
+      });
+    }
+  }
+}
+
 void AssetManager2::InvalidateCaches(uint32_t diff) {
   if (diff == 0xffffffffu) {
     // Everything must go.
@@ -872,7 +1027,7 @@
 ApkAssetsCookie Theme::ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
                                                  ResTable_config* in_out_selected_config,
                                                  uint32_t* in_out_type_spec_flags,
-                                                 uint32_t* out_last_ref) {
+                                                 uint32_t* out_last_ref) const {
   if (in_out_value->dataType == Res_value::TYPE_ATTRIBUTE) {
     uint32_t new_flags;
     cookie = GetAttribute(in_out_value->data, in_out_value, &new_flags);
diff --git a/libs/androidfw/AttributeResolution.cpp b/libs/androidfw/AttributeResolution.cpp
index 60e3845..f912af4 100644
--- a/libs/androidfw/AttributeResolution.cpp
+++ b/libs/androidfw/AttributeResolution.cpp
@@ -20,13 +20,18 @@
 
 #include <log/log.h>
 
+#include "androidfw/AssetManager2.h"
 #include "androidfw/AttributeFinder.h"
-#include "androidfw/ResourceTypes.h"
 
 constexpr bool kDebugStyles = false;
 
 namespace android {
 
+// Java asset cookies have 0 as an invalid cookie, but TypedArray expects < 0.
+static uint32_t ApkAssetsCookieToJavaCookie(ApkAssetsCookie cookie) {
+  return cookie != kInvalidCookie ? static_cast<uint32_t>(cookie + 1) : static_cast<uint32_t>(-1);
+}
+
 class XmlAttributeFinder
     : public BackTrackingAttributeFinder<XmlAttributeFinder, size_t> {
  public:
@@ -44,58 +49,53 @@
 };
 
 class BagAttributeFinder
-    : public BackTrackingAttributeFinder<BagAttributeFinder, const ResTable::bag_entry*> {
+    : public BackTrackingAttributeFinder<BagAttributeFinder, const ResolvedBag::Entry*> {
  public:
-  BagAttributeFinder(const ResTable::bag_entry* start,
-                     const ResTable::bag_entry* end)
-      : BackTrackingAttributeFinder(start, end) {}
+  BagAttributeFinder(const ResolvedBag* bag)
+      : BackTrackingAttributeFinder(bag != nullptr ? bag->entries : nullptr,
+                                    bag != nullptr ? bag->entries + bag->entry_count : nullptr) {
+  }
 
-  inline uint32_t GetAttribute(const ResTable::bag_entry* entry) const {
-    return entry->map.name.ident;
+  inline uint32_t GetAttribute(const ResolvedBag::Entry* entry) const {
+    return entry->key;
   }
 };
 
-bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr,
-                  uint32_t def_style_res, uint32_t* src_values,
-                  size_t src_values_length, uint32_t* attrs,
-                  size_t attrs_length, uint32_t* out_values,
-                  uint32_t* out_indices) {
+bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_res,
+                  uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
+                  size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
   if (kDebugStyles) {
     ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x", theme,
           def_style_attr, def_style_res);
   }
 
-  const ResTable& res = theme->getResTable();
+  AssetManager2* assetmanager = theme->GetAssetManager();
   ResTable_config config;
   Res_value value;
 
   int indices_idx = 0;
 
   // Load default style from attribute, if specified...
-  uint32_t def_style_bag_type_set_flags = 0;
+  uint32_t def_style_flags = 0u;
   if (def_style_attr != 0) {
     Res_value value;
-    if (theme->getAttribute(def_style_attr, &value, &def_style_bag_type_set_flags) >= 0) {
+    if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
       if (value.dataType == Res_value::TYPE_REFERENCE) {
         def_style_res = value.data;
       }
     }
   }
 
-  // Now lock down the resource object and start pulling stuff from it.
-  res.lock();
-
   // Retrieve the default style bag, if requested.
-  const ResTable::bag_entry* def_style_start = nullptr;
-  uint32_t def_style_type_set_flags = 0;
-  ssize_t bag_off = def_style_res != 0
-                        ? res.getBagLocked(def_style_res, &def_style_start,
-                                           &def_style_type_set_flags)
-                        : -1;
-  def_style_type_set_flags |= def_style_bag_type_set_flags;
-  const ResTable::bag_entry* const def_style_end =
-      def_style_start + (bag_off >= 0 ? bag_off : 0);
-  BagAttributeFinder def_style_attr_finder(def_style_start, def_style_end);
+  const ResolvedBag* default_style_bag = nullptr;
+  if (def_style_res != 0) {
+    default_style_bag = assetmanager->GetBag(def_style_res);
+    if (default_style_bag != nullptr) {
+      def_style_flags |= default_style_bag->type_spec_flags;
+    }
+  }
+
+  BagAttributeFinder def_style_attr_finder(default_style_bag);
 
   // Now iterate through all of the attributes that the client has requested,
   // filling in each with whatever data we can find.
@@ -106,7 +106,7 @@
       ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
     }
 
-    ssize_t block = -1;
+    ApkAssetsCookie cookie = kInvalidCookie;
     uint32_t type_set_flags = 0;
 
     value.dataType = Res_value::TYPE_NULL;
@@ -122,15 +122,14 @@
       value.dataType = Res_value::TYPE_ATTRIBUTE;
       value.data = src_values[ii];
       if (kDebugStyles) {
-        ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType,
-              value.data);
+        ALOGI("-> From values: type=0x%x, data=0x%08x", value.dataType, value.data);
       }
     } else {
-      const ResTable::bag_entry* const def_style_entry = def_style_attr_finder.Find(cur_ident);
-      if (def_style_entry != def_style_end) {
-        block = def_style_entry->stringBlock;
-        type_set_flags = def_style_type_set_flags;
-        value = def_style_entry->map.value;
+      const ResolvedBag::Entry* const entry = def_style_attr_finder.Find(cur_ident);
+      if (entry != def_style_attr_finder.end()) {
+        cookie = entry->cookie;
+        type_set_flags = def_style_flags;
+        value = entry->value;
         if (kDebugStyles) {
           ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
         }
@@ -140,22 +139,26 @@
     uint32_t resid = 0;
     if (value.dataType != Res_value::TYPE_NULL) {
       // Take care of resolving the found resource to its final value.
-      ssize_t new_block =
-          theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config);
-      if (new_block >= 0) block = new_block;
+      ApkAssetsCookie new_cookie =
+          theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
+      if (new_cookie != kInvalidCookie) {
+        cookie = new_cookie;
+      }
       if (kDebugStyles) {
         ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data);
       }
     } else if (value.data != Res_value::DATA_NULL_EMPTY) {
-      // If we still don't have a value for this attribute, try to find
-      // it in the theme!
-      ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags);
-      if (new_block >= 0) {
+      // If we still don't have a value for this attribute, try to find it in the theme!
+      ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
+      if (new_cookie != kInvalidCookie) {
         if (kDebugStyles) {
           ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
         }
-        new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config);
-        if (new_block >= 0) block = new_block;
+        new_cookie =
+            assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
+        if (new_cookie != kInvalidCookie) {
+          cookie = new_cookie;
+        }
         if (kDebugStyles) {
           ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data);
         }
@@ -169,7 +172,7 @@
       }
       value.dataType = Res_value::TYPE_NULL;
       value.data = Res_value::DATA_NULL_UNDEFINED;
-      block = -1;
+      cookie = kInvalidCookie;
     }
 
     if (kDebugStyles) {
@@ -179,9 +182,7 @@
     // Write the final value back to Java.
     out_values[STYLE_TYPE] = value.dataType;
     out_values[STYLE_DATA] = value.data;
-    out_values[STYLE_ASSET_COOKIE] =
-        block != -1 ? static_cast<uint32_t>(res.getTableCookie(block))
-                    : static_cast<uint32_t>(-1);
+    out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
     out_values[STYLE_RESOURCE_ID] = resid;
     out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
     out_values[STYLE_DENSITY] = config.density;
@@ -195,90 +196,80 @@
     out_values += STYLE_NUM_ENTRIES;
   }
 
-  res.unlock();
-
   if (out_indices != nullptr) {
     out_indices[0] = indices_idx;
   }
   return true;
 }
 
-void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
-                uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length,
+void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
+                uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length,
                 uint32_t* out_values, uint32_t* out_indices) {
   if (kDebugStyles) {
-    ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p",
-          theme, def_style_attr, def_style_res, xml_parser);
+    ALOGI("APPLY STYLE: theme=0x%p defStyleAttr=0x%x defStyleRes=0x%x xml=0x%p", theme,
+          def_style_attr, def_style_resid, xml_parser);
   }
 
-  const ResTable& res = theme->getResTable();
+  AssetManager2* assetmanager = theme->GetAssetManager();
   ResTable_config config;
   Res_value value;
 
   int indices_idx = 0;
 
   // Load default style from attribute, if specified...
-  uint32_t def_style_bag_type_set_flags = 0;
+  uint32_t def_style_flags = 0u;
   if (def_style_attr != 0) {
     Res_value value;
-    if (theme->getAttribute(def_style_attr, &value,
-                            &def_style_bag_type_set_flags) >= 0) {
+    if (theme->GetAttribute(def_style_attr, &value, &def_style_flags) != kInvalidCookie) {
       if (value.dataType == Res_value::TYPE_REFERENCE) {
-        def_style_res = value.data;
+        def_style_resid = value.data;
       }
     }
   }
 
-  // Retrieve the style class associated with the current XML tag.
-  int style = 0;
-  uint32_t style_bag_type_set_flags = 0;
+  // Retrieve the style resource ID associated with the current XML tag's style attribute.
+  uint32_t style_resid = 0u;
+  uint32_t style_flags = 0u;
   if (xml_parser != nullptr) {
     ssize_t idx = xml_parser->indexOfStyle();
     if (idx >= 0 && xml_parser->getAttributeValue(idx, &value) >= 0) {
       if (value.dataType == value.TYPE_ATTRIBUTE) {
-        if (theme->getAttribute(value.data, &value, &style_bag_type_set_flags) < 0) {
+        // Resolve the attribute with out theme.
+        if (theme->GetAttribute(value.data, &value, &style_flags) == kInvalidCookie) {
           value.dataType = Res_value::TYPE_NULL;
         }
       }
+
       if (value.dataType == value.TYPE_REFERENCE) {
-        style = value.data;
+        style_resid = value.data;
       }
     }
   }
 
-  // Now lock down the resource object and start pulling stuff from it.
-  res.lock();
-
   // Retrieve the default style bag, if requested.
-  const ResTable::bag_entry* def_style_attr_start = nullptr;
-  uint32_t def_style_type_set_flags = 0;
-  ssize_t bag_off = def_style_res != 0
-                        ? res.getBagLocked(def_style_res, &def_style_attr_start,
-                                           &def_style_type_set_flags)
-                        : -1;
-  def_style_type_set_flags |= def_style_bag_type_set_flags;
-  const ResTable::bag_entry* const def_style_attr_end =
-      def_style_attr_start + (bag_off >= 0 ? bag_off : 0);
-  BagAttributeFinder def_style_attr_finder(def_style_attr_start,
-                                           def_style_attr_end);
+  const ResolvedBag* default_style_bag = nullptr;
+  if (def_style_resid != 0) {
+    default_style_bag = assetmanager->GetBag(def_style_resid);
+    if (default_style_bag != nullptr) {
+      def_style_flags |= default_style_bag->type_spec_flags;
+    }
+  }
+
+  BagAttributeFinder def_style_attr_finder(default_style_bag);
 
   // Retrieve the style class bag, if requested.
-  const ResTable::bag_entry* style_attr_start = nullptr;
-  uint32_t style_type_set_flags = 0;
-  bag_off =
-      style != 0
-          ? res.getBagLocked(style, &style_attr_start, &style_type_set_flags)
-          : -1;
-  style_type_set_flags |= style_bag_type_set_flags;
-  const ResTable::bag_entry* const style_attr_end =
-      style_attr_start + (bag_off >= 0 ? bag_off : 0);
-  BagAttributeFinder style_attr_finder(style_attr_start, style_attr_end);
+  const ResolvedBag* xml_style_bag = nullptr;
+  if (style_resid != 0) {
+    xml_style_bag = assetmanager->GetBag(style_resid);
+    if (xml_style_bag != nullptr) {
+      style_flags |= xml_style_bag->type_spec_flags;
+    }
+  }
+
+  BagAttributeFinder xml_style_attr_finder(xml_style_bag);
 
   // Retrieve the XML attributes, if requested.
-  static const ssize_t kXmlBlock = 0x10000000;
   XmlAttributeFinder xml_attr_finder(xml_parser);
-  const size_t xml_attr_end =
-      xml_parser != nullptr ? xml_parser->getAttributeCount() : 0;
 
   // Now iterate through all of the attributes that the client has requested,
   // filling in each with whatever data we can find.
@@ -289,8 +280,8 @@
       ALOGI("RETRIEVING ATTR 0x%08x...", cur_ident);
     }
 
-    ssize_t block = kXmlBlock;
-    uint32_t type_set_flags = 0;
+    ApkAssetsCookie cookie = kInvalidCookie;
+    uint32_t type_set_flags = 0u;
 
     value.dataType = Res_value::TYPE_NULL;
     value.data = Res_value::DATA_NULL_UNDEFINED;
@@ -302,7 +293,7 @@
 
     // Walk through the xml attributes looking for the requested attribute.
     const size_t xml_attr_idx = xml_attr_finder.Find(cur_ident);
-    if (xml_attr_idx != xml_attr_end) {
+    if (xml_attr_idx != xml_attr_finder.end()) {
       // We found the attribute we were looking for.
       xml_parser->getAttributeValue(xml_attr_idx, &value);
       if (kDebugStyles) {
@@ -312,12 +303,12 @@
 
     if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
       // Walk through the style class values looking for the requested attribute.
-      const ResTable::bag_entry* const style_attr_entry = style_attr_finder.Find(cur_ident);
-      if (style_attr_entry != style_attr_end) {
+      const ResolvedBag::Entry* entry = xml_style_attr_finder.Find(cur_ident);
+      if (entry != xml_style_attr_finder.end()) {
         // We found the attribute we were looking for.
-        block = style_attr_entry->stringBlock;
-        type_set_flags = style_type_set_flags;
-        value = style_attr_entry->map.value;
+        cookie = entry->cookie;
+        type_set_flags = style_flags;
+        value = entry->value;
         if (kDebugStyles) {
           ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data);
         }
@@ -326,25 +317,25 @@
 
     if (value.dataType == Res_value::TYPE_NULL && value.data != Res_value::DATA_NULL_EMPTY) {
       // Walk through the default style values looking for the requested attribute.
-      const ResTable::bag_entry* const def_style_attr_entry = def_style_attr_finder.Find(cur_ident);
-      if (def_style_attr_entry != def_style_attr_end) {
+      const ResolvedBag::Entry* entry = def_style_attr_finder.Find(cur_ident);
+      if (entry != def_style_attr_finder.end()) {
         // We found the attribute we were looking for.
-        block = def_style_attr_entry->stringBlock;
-        type_set_flags = style_type_set_flags;
-        value = def_style_attr_entry->map.value;
+        cookie = entry->cookie;
+        type_set_flags = def_style_flags;
+        value = entry->value;
         if (kDebugStyles) {
           ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data);
         }
       }
     }
 
-    uint32_t resid = 0;
+    uint32_t resid = 0u;
     if (value.dataType != Res_value::TYPE_NULL) {
       // Take care of resolving the found resource to its final value.
-      ssize_t new_block =
-          theme->resolveAttributeReference(&value, block, &resid, &type_set_flags, &config);
-      if (new_block >= 0) {
-        block = new_block;
+      ApkAssetsCookie new_cookie =
+          theme->ResolveAttributeReference(cookie, &value, &config, &type_set_flags, &resid);
+      if (new_cookie != kInvalidCookie) {
+        cookie = new_cookie;
       }
 
       if (kDebugStyles) {
@@ -352,14 +343,15 @@
       }
     } else if (value.data != Res_value::DATA_NULL_EMPTY) {
       // If we still don't have a value for this attribute, try to find it in the theme!
-      ssize_t new_block = theme->getAttribute(cur_ident, &value, &type_set_flags);
-      if (new_block >= 0) {
+      ApkAssetsCookie new_cookie = theme->GetAttribute(cur_ident, &value, &type_set_flags);
+      if (new_cookie != kInvalidCookie) {
         if (kDebugStyles) {
           ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data);
         }
-        new_block = res.resolveReference(&value, new_block, &resid, &type_set_flags, &config);
-        if (new_block >= 0) {
-          block = new_block;
+        new_cookie =
+            assetmanager->ResolveReference(new_cookie, &value, &config, &type_set_flags, &resid);
+        if (new_cookie != kInvalidCookie) {
+          cookie = new_cookie;
         }
 
         if (kDebugStyles) {
@@ -375,7 +367,7 @@
       }
       value.dataType = Res_value::TYPE_NULL;
       value.data = Res_value::DATA_NULL_UNDEFINED;
-      block = kXmlBlock;
+      cookie = kInvalidCookie;
     }
 
     if (kDebugStyles) {
@@ -385,9 +377,7 @@
     // Write the final value back to Java.
     out_values[STYLE_TYPE] = value.dataType;
     out_values[STYLE_DATA] = value.data;
-    out_values[STYLE_ASSET_COOKIE] =
-        block != kXmlBlock ? static_cast<uint32_t>(res.getTableCookie(block))
-                           : static_cast<uint32_t>(-1);
+    out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
     out_values[STYLE_RESOURCE_ID] = resid;
     out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
     out_values[STYLE_DENSITY] = config.density;
@@ -402,36 +392,28 @@
     out_values += STYLE_NUM_ENTRIES;
   }
 
-  res.unlock();
-
   // out_indices must NOT be nullptr.
   out_indices[0] = indices_idx;
 }
 
-bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser,
-                        uint32_t* attrs, size_t attrs_length,
-                        uint32_t* out_values, uint32_t* out_indices) {
+bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs,
+                        size_t attrs_length, uint32_t* out_values, uint32_t* out_indices) {
   ResTable_config config;
   Res_value value;
 
   int indices_idx = 0;
 
-  // Now lock down the resource object and start pulling stuff from it.
-  res->lock();
-
   // Retrieve the XML attributes, if requested.
   const size_t xml_attr_count = xml_parser->getAttributeCount();
   size_t ix = 0;
   uint32_t cur_xml_attr = xml_parser->getAttributeNameResID(ix);
 
-  static const ssize_t kXmlBlock = 0x10000000;
-
   // Now iterate through all of the attributes that the client has requested,
   // filling in each with whatever data we can find.
   for (size_t ii = 0; ii < attrs_length; ii++) {
     const uint32_t cur_ident = attrs[ii];
-    ssize_t block = kXmlBlock;
-    uint32_t type_set_flags = 0;
+    ApkAssetsCookie cookie = kInvalidCookie;
+    uint32_t type_set_flags = 0u;
 
     value.dataType = Res_value::TYPE_NULL;
     value.data = Res_value::DATA_NULL_UNDEFINED;
@@ -450,28 +432,27 @@
       cur_xml_attr = xml_parser->getAttributeNameResID(ix);
     }
 
-    uint32_t resid = 0;
+    uint32_t resid = 0u;
     if (value.dataType != Res_value::TYPE_NULL) {
       // Take care of resolving the found resource to its final value.
-      // printf("Resolving attribute reference\n");
-      ssize_t new_block = res->resolveReference(&value, block, &resid,
-                                                &type_set_flags, &config);
-      if (new_block >= 0) block = new_block;
+      ApkAssetsCookie new_cookie =
+          assetmanager->ResolveReference(cookie, &value, &config, &type_set_flags, &resid);
+      if (new_cookie != kInvalidCookie) {
+        cookie = new_cookie;
+      }
     }
 
     // Deal with the special @null value -- it turns back to TYPE_NULL.
     if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) {
       value.dataType = Res_value::TYPE_NULL;
       value.data = Res_value::DATA_NULL_UNDEFINED;
-      block = kXmlBlock;
+      cookie = kInvalidCookie;
     }
 
     // Write the final value back to Java.
     out_values[STYLE_TYPE] = value.dataType;
     out_values[STYLE_DATA] = value.data;
-    out_values[STYLE_ASSET_COOKIE] =
-        block != kXmlBlock ? static_cast<uint32_t>(res->getTableCookie(block))
-                           : static_cast<uint32_t>(-1);
+    out_values[STYLE_ASSET_COOKIE] = ApkAssetsCookieToJavaCookie(cookie);
     out_values[STYLE_RESOURCE_ID] = resid;
     out_values[STYLE_CHANGING_CONFIGURATIONS] = type_set_flags;
     out_values[STYLE_DENSITY] = config.density;
@@ -485,8 +466,6 @@
     out_values += STYLE_NUM_ENTRIES;
   }
 
-  res->unlock();
-
   if (out_indices != nullptr) {
     out_indices[0] = indices_idx;
   }
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp
index 28548e2..1d2c597 100644
--- a/libs/androidfw/LoadedArsc.cpp
+++ b/libs/androidfw/LoadedArsc.cpp
@@ -44,44 +44,6 @@
 
 constexpr const static int kAppPackageId = 0x7f;
 
-// Element of a TypeSpec array. See TypeSpec.
-struct Type {
-  // The configuration for which this type defines entries.
-  // This is already converted to host endianness.
-  ResTable_config configuration;
-
-  // Pointer to the mmapped data where entry definitions are kept.
-  const ResTable_type* type;
-};
-
-// TypeSpec is going to be immediately proceeded by
-// an array of Type structs, all in the same block of memory.
-struct TypeSpec {
-  // Pointer to the mmapped data where flags are kept.
-  // Flags denote whether the resource entry is public
-  // and under which configurations it varies.
-  const ResTable_typeSpec* type_spec;
-
-  // Pointer to the mmapped data where the IDMAP mappings for this type
-  // exist. May be nullptr if no IDMAP exists.
-  const IdmapEntry_header* idmap_entries;
-
-  // The number of types that follow this struct.
-  // There is a type for each configuration
-  // that entries are defined for.
-  size_t type_count;
-
-  // Trick to easily access a variable number of Type structs
-  // proceeding this struct, and to ensure their alignment.
-  const Type types[0];
-};
-
-// TypeSpecPtr points to the block of memory that holds
-// a TypeSpec struct, followed by an array of Type structs.
-// TypeSpecPtr is a managed pointer that knows how to delete
-// itself.
-using TypeSpecPtr = util::unique_cptr<TypeSpec>;
-
 namespace {
 
 // Builder that helps accumulate Type structs and then create a single
@@ -95,21 +57,22 @@
   }
 
   void AddType(const ResTable_type* type) {
-    ResTable_config config;
-    config.copyFromDtoH(type->config);
-    types_.push_back(Type{config, type});
+    types_.push_back(type);
   }
 
   TypeSpecPtr Build() {
     // Check for overflow.
-    if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(Type) < types_.size()) {
+    using ElementType = const ResTable_type*;
+    if ((std::numeric_limits<size_t>::max() - sizeof(TypeSpec)) / sizeof(ElementType) <
+        types_.size()) {
       return {};
     }
-    TypeSpec* type_spec = (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(Type)));
+    TypeSpec* type_spec =
+        (TypeSpec*)::malloc(sizeof(TypeSpec) + (types_.size() * sizeof(ElementType)));
     type_spec->type_spec = header_;
     type_spec->idmap_entries = idmap_header_;
     type_spec->type_count = types_.size();
-    memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(Type));
+    memcpy(type_spec + 1, types_.data(), types_.size() * sizeof(ElementType));
     return TypeSpecPtr(type_spec);
   }
 
@@ -118,7 +81,7 @@
 
   const ResTable_typeSpec* header_;
   const IdmapEntry_header* idmap_header_;
-  std::vector<Type> types_;
+  std::vector<const ResTable_type*> types_;
 };
 
 }  // namespace
@@ -162,18 +125,17 @@
   return true;
 }
 
-static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset,
-                                size_t entry_idx) {
+static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset) {
   // Check that the offset is aligned.
   if (entry_offset & 0x03) {
-    LOG(ERROR) << "Entry offset at index " << entry_idx << " is not 4-byte aligned.";
+    LOG(ERROR) << "Entry at offset " << entry_offset << " is not 4-byte aligned.";
     return false;
   }
 
   // Check that the offset doesn't overflow.
   if (entry_offset > std::numeric_limits<uint32_t>::max() - dtohl(type->entriesStart)) {
     // Overflow in offset.
-    LOG(ERROR) << "Entry offset at index " << entry_idx << " is too large.";
+    LOG(ERROR) << "Entry at offset " << entry_offset << " is too large.";
     return false;
   }
 
@@ -181,7 +143,7 @@
 
   entry_offset += dtohl(type->entriesStart);
   if (entry_offset > chunk_size - sizeof(ResTable_entry)) {
-    LOG(ERROR) << "Entry offset at index " << entry_idx
+    LOG(ERROR) << "Entry at offset " << entry_offset
                << " is too large. No room for ResTable_entry.";
     return false;
   }
@@ -191,13 +153,13 @@
 
   const size_t entry_size = dtohs(entry->size);
   if (entry_size < sizeof(*entry)) {
-    LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
+    LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
                << " is too small.";
     return false;
   }
 
   if (entry_size > chunk_size || entry_offset > chunk_size - entry_size) {
-    LOG(ERROR) << "ResTable_entry size " << entry_size << " at index " << entry_idx
+    LOG(ERROR) << "ResTable_entry size " << entry_size << " at offset " << entry_offset
                << " is too large.";
     return false;
   }
@@ -205,7 +167,7 @@
   if (entry_size < sizeof(ResTable_map_entry)) {
     // There needs to be room for one Res_value struct.
     if (entry_offset + entry_size > chunk_size - sizeof(Res_value)) {
-      LOG(ERROR) << "No room for Res_value after ResTable_entry at index " << entry_idx
+      LOG(ERROR) << "No room for Res_value after ResTable_entry at offset " << entry_offset
                  << " for type " << (int)type->id << ".";
       return false;
     }
@@ -214,12 +176,12 @@
         reinterpret_cast<const Res_value*>(reinterpret_cast<const uint8_t*>(entry) + entry_size);
     const size_t value_size = dtohs(value->size);
     if (value_size < sizeof(Res_value)) {
-      LOG(ERROR) << "Res_value at index " << entry_idx << " is too small.";
+      LOG(ERROR) << "Res_value at offset " << entry_offset << " is too small.";
       return false;
     }
 
     if (value_size > chunk_size || entry_offset + entry_size > chunk_size - value_size) {
-      LOG(ERROR) << "Res_value size " << value_size << " at index " << entry_idx
+      LOG(ERROR) << "Res_value size " << value_size << " at offset " << entry_offset
                  << " is too large.";
       return false;
     }
@@ -228,119 +190,76 @@
     const size_t map_entry_count = dtohl(map->count);
     size_t map_entries_start = entry_offset + entry_size;
     if (map_entries_start & 0x03) {
-      LOG(ERROR) << "Map entries at index " << entry_idx << " start at unaligned offset.";
+      LOG(ERROR) << "Map entries at offset " << entry_offset << " start at unaligned offset.";
       return false;
     }
 
     // Each entry is sizeof(ResTable_map) big.
     if (map_entry_count > ((chunk_size - map_entries_start) / sizeof(ResTable_map))) {
-      LOG(ERROR) << "Too many map entries in ResTable_map_entry at index " << entry_idx << ".";
+      LOG(ERROR) << "Too many map entries in ResTable_map_entry at offset " << entry_offset << ".";
       return false;
     }
   }
   return true;
 }
 
-bool LoadedPackage::FindEntry(const TypeSpecPtr& type_spec_ptr, uint16_t entry_idx,
-                              const ResTable_config& config, FindEntryResult* out_entry) const {
-  const ResTable_config* best_config = nullptr;
-  const ResTable_type* best_type = nullptr;
-  uint32_t best_offset = 0;
+const ResTable_entry* LoadedPackage::GetEntry(const ResTable_type* type_chunk,
+                                              uint16_t entry_index) {
+  uint32_t entry_offset = GetEntryOffset(type_chunk, entry_index);
+  if (entry_offset == ResTable_type::NO_ENTRY) {
+    return nullptr;
+  }
+  return GetEntryFromOffset(type_chunk, entry_offset);
+}
 
-  for (uint32_t i = 0; i < type_spec_ptr->type_count; i++) {
-    const Type* type = &type_spec_ptr->types[i];
-    const ResTable_type* type_chunk = type->type;
+uint32_t LoadedPackage::GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index) {
+  // The configuration matches and is better than the previous selection.
+  // Find the entry value if it exists for this configuration.
+  const size_t entry_count = dtohl(type_chunk->entryCount);
+  const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
 
-    if (type->configuration.match(config) &&
-        (best_config == nullptr || type->configuration.isBetterThan(*best_config, &config))) {
-      // The configuration matches and is better than the previous selection.
-      // Find the entry value if it exists for this configuration.
-      const size_t entry_count = dtohl(type_chunk->entryCount);
-      const size_t offsets_offset = dtohs(type_chunk->header.headerSize);
+  // Check if there is the desired entry in this type.
 
-      // Check if there is the desired entry in this type.
-
-      if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
-        // This is encoded as a sparse map, so perform a binary search.
-        const ResTable_sparseTypeEntry* sparse_indices =
-            reinterpret_cast<const ResTable_sparseTypeEntry*>(
-                reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
-        const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count;
-        const ResTable_sparseTypeEntry* result =
-            std::lower_bound(sparse_indices, sparse_indices_end, entry_idx,
-                             [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) {
-                               return dtohs(entry.idx) < entry_idx;
-                             });
-
-        if (result == sparse_indices_end || dtohs(result->idx) != entry_idx) {
-          // No entry found.
-          continue;
-        }
-
-        // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as
-        // the real offset divided by 4.
-        best_offset = uint32_t{dtohs(result->offset)} * 4u;
-      } else {
-        if (entry_idx >= entry_count) {
-          // This entry cannot be here.
-          continue;
-        }
-
-        const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
+  if (type_chunk->flags & ResTable_type::FLAG_SPARSE) {
+    // This is encoded as a sparse map, so perform a binary search.
+    const ResTable_sparseTypeEntry* sparse_indices =
+        reinterpret_cast<const ResTable_sparseTypeEntry*>(
             reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
-        const uint32_t offset = dtohl(entry_offsets[entry_idx]);
-        if (offset == ResTable_type::NO_ENTRY) {
-          continue;
-        }
+    const ResTable_sparseTypeEntry* sparse_indices_end = sparse_indices + entry_count;
+    const ResTable_sparseTypeEntry* result =
+        std::lower_bound(sparse_indices, sparse_indices_end, entry_index,
+                         [](const ResTable_sparseTypeEntry& entry, uint16_t entry_idx) {
+                           return dtohs(entry.idx) < entry_idx;
+                         });
 
-        // There is an entry for this resource, record it.
-        best_offset = offset;
-      }
-
-      best_config = &type->configuration;
-      best_type = type_chunk;
+    if (result == sparse_indices_end || dtohs(result->idx) != entry_index) {
+      // No entry found.
+      return ResTable_type::NO_ENTRY;
     }
+
+    // Extract the offset from the entry. Each offset must be a multiple of 4 so we store it as
+    // the real offset divided by 4.
+    return uint32_t{dtohs(result->offset)} * 4u;
   }
 
-  if (best_type == nullptr) {
-    return false;
+  // This type is encoded as a dense array.
+  if (entry_index >= entry_count) {
+    // This entry cannot be here.
+    return ResTable_type::NO_ENTRY;
   }
 
-  if (UNLIKELY(!VerifyResTableEntry(best_type, best_offset, entry_idx))) {
-    return false;
-  }
-
-  const ResTable_entry* best_entry = reinterpret_cast<const ResTable_entry*>(
-      reinterpret_cast<const uint8_t*>(best_type) + best_offset + dtohl(best_type->entriesStart));
-
-  const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec_ptr->type_spec + 1);
-  out_entry->type_flags = dtohl(flags[entry_idx]);
-  out_entry->entry = best_entry;
-  out_entry->config = best_config;
-  out_entry->type_string_ref = StringPoolRef(&type_string_pool_, best_type->id - 1);
-  out_entry->entry_string_ref = StringPoolRef(&key_string_pool_, dtohl(best_entry->key.index));
-  return true;
+  const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
+      reinterpret_cast<const uint8_t*>(type_chunk) + offsets_offset);
+  return dtohl(entry_offsets[entry_index]);
 }
 
-bool LoadedPackage::FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
-                              FindEntryResult* out_entry) const {
-  ATRACE_CALL();
-
-  // If the type IDs are offset in this package, we need to take that into account when searching
-  // for a type.
-  const TypeSpecPtr& ptr = type_specs_[type_idx - type_id_offset_];
-  if (UNLIKELY(ptr == nullptr)) {
-    return false;
+const ResTable_entry* LoadedPackage::GetEntryFromOffset(const ResTable_type* type_chunk,
+                                                        uint32_t offset) {
+  if (UNLIKELY(!VerifyResTableEntry(type_chunk, offset))) {
+    return nullptr;
   }
-
-  // If there is an IDMAP supplied with this package, translate the entry ID.
-  if (ptr->idmap_entries != nullptr) {
-    if (!LoadedIdmap::Lookup(ptr->idmap_entries, entry_idx, &entry_idx)) {
-      // There is no mapping, so the resource is not meant to be in this overlay package.
-      return false;
-    }
-  }
-  return FindEntry(ptr, entry_idx, config, out_entry);
+  return reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type_chunk) +
+                                                 offset + dtohl(type_chunk->entriesStart));
 }
 
 void LoadedPackage::CollectConfigurations(bool exclude_mipmap,
@@ -348,7 +267,7 @@
   const static std::u16string kMipMap = u"mipmap";
   const size_t type_count = type_specs_.size();
   for (size_t i = 0; i < type_count; i++) {
-    const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i];
+    const TypeSpecPtr& type_spec = type_specs_[i];
     if (type_spec != nullptr) {
       if (exclude_mipmap) {
         const int type_idx = type_spec->type_spec->id - 1;
@@ -369,8 +288,11 @@
         }
       }
 
-      for (size_t j = 0; j < type_spec->type_count; j++) {
-        out_configs->insert(type_spec->types[j].configuration);
+      const auto iter_end = type_spec->types + type_spec->type_count;
+      for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+        ResTable_config config;
+        config.copyFromDtoH((*iter)->config);
+        out_configs->insert(config);
       }
     }
   }
@@ -380,10 +302,12 @@
   char temp_locale[RESTABLE_MAX_LOCALE_LEN];
   const size_t type_count = type_specs_.size();
   for (size_t i = 0; i < type_count; i++) {
-    const util::unique_cptr<TypeSpec>& type_spec = type_specs_[i];
+    const TypeSpecPtr& type_spec = type_specs_[i];
     if (type_spec != nullptr) {
-      for (size_t j = 0; j < type_spec->type_count; j++) {
-        const ResTable_config& configuration = type_spec->types[j].configuration;
+      const auto iter_end = type_spec->types + type_spec->type_count;
+      for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+        ResTable_config configuration;
+        configuration.copyFromDtoH((*iter)->config);
         if (configuration.locale != 0) {
           configuration.getBcp47Locale(temp_locale, canonicalize);
           std::string locale(temp_locale);
@@ -411,17 +335,17 @@
     return 0u;
   }
 
-  for (size_t ti = 0; ti < type_spec->type_count; ti++) {
-    const Type* type = &type_spec->types[ti];
-    size_t entry_count = dtohl(type->type->entryCount);
+  const auto iter_end = type_spec->types + type_spec->type_count;
+  for (auto iter = type_spec->types; iter != iter_end; ++iter) {
+    const ResTable_type* type = *iter;
+    size_t entry_count = dtohl(type->entryCount);
     for (size_t entry_idx = 0; entry_idx < entry_count; entry_idx++) {
       const uint32_t* entry_offsets = reinterpret_cast<const uint32_t*>(
-          reinterpret_cast<const uint8_t*>(type->type) + dtohs(type->type->header.headerSize));
+          reinterpret_cast<const uint8_t*>(type) + dtohs(type->header.headerSize));
       const uint32_t offset = dtohl(entry_offsets[entry_idx]);
       if (offset != ResTable_type::NO_ENTRY) {
-        const ResTable_entry* entry =
-            reinterpret_cast<const ResTable_entry*>(reinterpret_cast<const uint8_t*>(type->type) +
-                                                    dtohl(type->type->entriesStart) + offset);
+        const ResTable_entry* entry = reinterpret_cast<const ResTable_entry*>(
+            reinterpret_cast<const uint8_t*>(type) + dtohl(type->entriesStart) + offset);
         if (dtohl(entry->key.index) == static_cast<uint32_t>(key_idx)) {
           // The package ID will be overridden by the caller (due to runtime assignment of package
           // IDs for shared libraries).
@@ -433,8 +357,7 @@
   return 0u;
 }
 
-const LoadedPackage* LoadedArsc::GetPackageForId(uint32_t resid) const {
-  const uint8_t package_id = get_package_id(resid);
+const LoadedPackage* LoadedArsc::GetPackageById(uint8_t package_id) const {
   for (const auto& loaded_package : packages_) {
     if (loaded_package->GetPackageId() == package_id) {
       return loaded_package.get();
@@ -682,26 +605,6 @@
   return std::move(loaded_package);
 }
 
-bool LoadedArsc::FindEntry(uint32_t resid, const ResTable_config& config,
-                           FindEntryResult* out_entry) const {
-  ATRACE_CALL();
-
-  const uint8_t package_id = get_package_id(resid);
-  const uint8_t type_id = get_type_id(resid);
-  const uint16_t entry_id = get_entry_id(resid);
-
-  if (UNLIKELY(type_id == 0)) {
-    LOG(ERROR) << base::StringPrintf("Invalid ID 0x%08x.", resid);
-    return false;
-  }
-
-  for (const auto& loaded_package : packages_) {
-    if (loaded_package->GetPackageId() == package_id) {
-      return loaded_package->FindEntry(type_id - 1, entry_id, config, out_entry);
-    }
-  }
-  return false;
-}
 
 bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
                            bool load_as_shared_library) {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index b033137..ef08897 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -69,6 +69,8 @@
   Entry entries[0];
 };
 
+struct FindEntryResult;
+
 // AssetManager2 is the main entry point for accessing assets and resources.
 // AssetManager2 provides caching of resources retrieved via the underlying ApkAssets.
 class AssetManager2 {
@@ -127,7 +129,7 @@
   // If `exclude_mipmap` is set to true, resource configurations defined for resource type 'mipmap'
   // will be excluded from the list.
   std::set<ResTable_config> GetResourceConfigurations(bool exclude_system = false,
-                                                      bool exclude_mipmap = false);
+                                                      bool exclude_mipmap = false) const;
 
   // Returns all the locales for which there are resources defined. This includes resource
   // locales in all the ApkAssets set for this AssetManager.
@@ -136,24 +138,24 @@
   // If `merge_equivalent_languages` is set to true, resource locales will be canonicalized
   // and de-duped in the resulting list.
   std::set<std::string> GetResourceLocales(bool exclude_system = false,
-                                           bool merge_equivalent_languages = false);
+                                           bool merge_equivalent_languages = false) const;
 
   // Searches the set of APKs loaded by this AssetManager and opens the first one found located
   // in the assets/ directory.
   // `mode` controls how the file is opened.
   //
   // NOTE: The loaded APKs are searched in reverse order.
-  std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode);
+  std::unique_ptr<Asset> Open(const std::string& filename, Asset::AccessMode mode) const;
 
   // Opens a file within the assets/ directory of the APK specified by `cookie`.
   // `mode` controls how the file is opened.
   std::unique_ptr<Asset> Open(const std::string& filename, ApkAssetsCookie cookie,
-                              Asset::AccessMode mode);
+                              Asset::AccessMode mode) const;
 
   // Opens the directory specified by `dirname`. The result is an AssetDir that is the combination
   // of all directories matching `dirname` under the assets/ directory of every ApkAssets loaded.
   // The entries are sorted by their ASCII name.
-  std::unique_ptr<AssetDir> OpenDir(const std::string& dirname);
+  std::unique_ptr<AssetDir> OpenDir(const std::string& dirname) const;
 
   // Searches the set of APKs loaded by this AssetManager and opens the first one found.
   // `mode` controls how the file is opened.
@@ -161,24 +163,24 @@
   //
   // NOTE: The loaded APKs are searched in reverse order.
   std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, Asset::AccessMode mode,
-                                      ApkAssetsCookie* out_cookie = nullptr);
+                                      ApkAssetsCookie* out_cookie = nullptr) const;
 
   // Opens a file in the APK specified by `cookie`. `mode` controls how the file is opened.
   // This is typically used to open a specific AndroidManifest.xml, or a binary XML file
   // referenced by a resource lookup with GetResource().
   std::unique_ptr<Asset> OpenNonAsset(const std::string& filename, ApkAssetsCookie cookie,
-                                      Asset::AccessMode mode);
+                                      Asset::AccessMode mode) const;
 
   // Populates the `out_name` parameter with resource name information.
   // Utf8 strings are preferred, and only if they are unavailable are
   // the Utf16 variants populated.
   // Returns false if the resource was not found or the name was missing/corrupt.
-  bool GetResourceName(uint32_t resid, ResourceName* out_name);
+  bool GetResourceName(uint32_t resid, ResourceName* out_name) const;
 
   // Populates `out_flags` with the bitmask of configuration axis that this resource varies with.
   // See ResTable_config for the list of configuration axis.
   // Returns false if the resource was not found.
-  bool GetResourceFlags(uint32_t resid, uint32_t* out_flags);
+  bool GetResourceFlags(uint32_t resid, uint32_t* out_flags) const;
 
   // Finds the resource ID assigned to `resource_name`.
   // `resource_name` must be of the form '[package:][type/]entry'.
@@ -186,7 +188,7 @@
   // If no type is specified in `resource_name`, then `fallback_type` is used as the type.
   // Returns 0x0 if no resource by that name was found.
   uint32_t GetResourceId(const std::string& resource_name, const std::string& fallback_type = {},
-                         const std::string& fallback_package = {});
+                         const std::string& fallback_package = {}) const;
 
   // Retrieves the best matching resource with ID `resid`. The resource value is filled into
   // `out_value` and the configuration for the selected value is populated in `out_selected_config`.
@@ -199,7 +201,7 @@
   // this function logs if the resource was a map/bag type before returning kInvalidCookie.
   ApkAssetsCookie GetResource(uint32_t resid, bool may_be_bag, uint16_t density_override,
                               Res_value* out_value, ResTable_config* out_selected_config,
-                              uint32_t* out_flags);
+                              uint32_t* out_flags) const;
 
   // Resolves the resource reference in `in_out_value` if the data type is
   // Res_value::TYPE_REFERENCE.
@@ -215,7 +217,7 @@
   // it was not found.
   ApkAssetsCookie ResolveReference(ApkAssetsCookie cookie, Res_value* in_out_value,
                                    ResTable_config* in_out_selected_config, uint32_t* in_out_flags,
-                                   uint32_t* out_last_reference);
+                                   uint32_t* out_last_reference) const;
 
   // Retrieves the best matching bag/map resource with ID `resid`.
   // This method will resolve all parent references for this bag and merge keys with the child.
@@ -233,9 +235,9 @@
   std::unique_ptr<Theme> NewTheme();
 
   template <typename Func>
-  void ForEachPackage(Func func) {
+  void ForEachPackage(Func func) const {
     for (const PackageGroup& package_group : package_groups_) {
-      func(package_group.packages_.front()->GetPackageName(),
+      func(package_group.packages_.front().loaded_package_->GetPackageName(),
            package_group.dynamic_ref_table.mAssignedPackageId);
     }
   }
@@ -260,7 +262,7 @@
   // NOTE: FindEntry takes care of ensuring that structs within FindEntryResult have been properly
   // bounds-checked. Callers of FindEntry are free to trust the data if this method succeeds.
   ApkAssetsCookie FindEntry(uint32_t resid, uint16_t density_override, bool stop_at_first_match,
-                            FindEntryResult* out_entry);
+                            FindEntryResult* out_entry) const;
 
   // Assigns package IDs to all shared library ApkAssets.
   // Should be called whenever the ApkAssets are changed.
@@ -270,13 +272,43 @@
   // bitmask `diff`.
   void InvalidateCaches(uint32_t diff);
 
+  // Triggers the re-construction of lists of types that match the set configuration.
+  // This should always be called when mutating the AssetManager's configuration or ApkAssets set.
+  void RebuildFilterList();
+
   // The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
   // have a longer lifetime.
   std::vector<const ApkAssets*> apk_assets_;
 
+  // A collection of configurations and their associated ResTable_type that match the current
+  // AssetManager configuration.
+  struct FilteredConfigGroup {
+    std::vector<ResTable_config> configurations;
+    std::vector<const ResTable_type*> types;
+  };
+
+  // Represents an single package.
+  struct ConfiguredPackage {
+    // A pointer to the immutable, loaded package info.
+    const LoadedPackage* loaded_package_;
+
+    // A mutable AssetManager-specific list of configurations that match the AssetManager's
+    // current configuration. This is used as an optimization to avoid checking every single
+    // candidate configuration when looking up resources.
+    ByteBucketArray<FilteredConfigGroup> filtered_configs_;
+  };
+
+  // Represents a logical package, which can be made up of many individual packages. Each package
+  // in a PackageGroup shares the same package name and package ID.
   struct PackageGroup {
-    std::vector<const LoadedPackage*> packages_;
+    // The set of packages that make-up this group.
+    std::vector<ConfiguredPackage> packages_;
+
+    // The cookies associated with each package in the group. They share the same order as
+    // packages_.
     std::vector<ApkAssetsCookie> cookies_;
+
+    // A library reference table that contains build-package ID to runtime-package ID mappings.
     DynamicRefTable dynamic_ref_table;
   };
 
@@ -350,7 +382,7 @@
   ApkAssetsCookie ResolveAttributeReference(ApkAssetsCookie cookie, Res_value* in_out_value,
                                             ResTable_config* in_out_selected_config = nullptr,
                                             uint32_t* in_out_type_spec_flags = nullptr,
-                                            uint32_t* out_last_ref = nullptr);
+                                            uint32_t* out_last_ref = nullptr) const;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(Theme);
diff --git a/libs/androidfw/include/androidfw/AttributeFinder.h b/libs/androidfw/include/androidfw/AttributeFinder.h
index f281921..03fad49 100644
--- a/libs/androidfw/include/androidfw/AttributeFinder.h
+++ b/libs/androidfw/include/androidfw/AttributeFinder.h
@@ -58,6 +58,7 @@
   BackTrackingAttributeFinder(const Iterator& begin, const Iterator& end);
 
   Iterator Find(uint32_t attr);
+  inline Iterator end();
 
  private:
   void JumpToClosestAttribute(uint32_t package_id);
@@ -201,6 +202,11 @@
   return end_;
 }
 
+template <typename Derived, typename Iterator>
+Iterator BackTrackingAttributeFinder<Derived, Iterator>::end() {
+  return end_;
+}
+
 }  // namespace android
 
 #endif  // ANDROIDFW_ATTRIBUTE_FINDER_H
diff --git a/libs/androidfw/include/androidfw/AttributeResolution.h b/libs/androidfw/include/androidfw/AttributeResolution.h
index 69b76041..35ef98d 100644
--- a/libs/androidfw/include/androidfw/AttributeResolution.h
+++ b/libs/androidfw/include/androidfw/AttributeResolution.h
@@ -17,7 +17,8 @@
 #ifndef ANDROIDFW_ATTRIBUTERESOLUTION_H
 #define ANDROIDFW_ATTRIBUTERESOLUTION_H
 
-#include <androidfw/ResourceTypes.h>
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ResourceTypes.h"
 
 namespace android {
 
@@ -42,19 +43,19 @@
 
 // `out_values` must NOT be nullptr.
 // `out_indices` may be nullptr.
-bool ResolveAttrs(ResTable::Theme* theme, uint32_t def_style_attr, uint32_t def_style_res,
+bool ResolveAttrs(Theme* theme, uint32_t def_style_attr, uint32_t def_style_resid,
                   uint32_t* src_values, size_t src_values_length, uint32_t* attrs,
                   size_t attrs_length, uint32_t* out_values, uint32_t* out_indices);
 
 // `out_values` must NOT be nullptr.
 // `out_indices` is NOT optional and must NOT be nullptr.
-void ApplyStyle(ResTable::Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
-                uint32_t def_style_res, const uint32_t* attrs, size_t attrs_length,
+void ApplyStyle(Theme* theme, ResXMLParser* xml_parser, uint32_t def_style_attr,
+                uint32_t def_style_resid, const uint32_t* attrs, size_t attrs_length,
                 uint32_t* out_values, uint32_t* out_indices);
 
 // `out_values` must NOT be nullptr.
 // `out_indices` may be nullptr.
-bool RetrieveAttributes(const ResTable* res, ResXMLParser* xml_parser, uint32_t* attrs,
+bool RetrieveAttributes(AssetManager2* assetmanager, ResXMLParser* xml_parser, uint32_t* attrs,
                         size_t attrs_length, uint32_t* out_values, uint32_t* out_indices);
 
 }  // namespace android
diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h
index 965e2db..35ae5fc 100644
--- a/libs/androidfw/include/androidfw/LoadedArsc.h
+++ b/libs/androidfw/include/androidfw/LoadedArsc.h
@@ -41,32 +41,40 @@
   int package_id = 0;
 };
 
-struct FindEntryResult {
-  // A pointer to the resource table entry for this resource.
-  // If the size of the entry is > sizeof(ResTable_entry), it can be cast to
-  // a ResTable_map_entry and processed as a bag/map.
-  const ResTable_entry* entry = nullptr;
+// TypeSpec is going to be immediately proceeded by
+// an array of Type structs, all in the same block of memory.
+struct TypeSpec {
+  // Pointer to the mmapped data where flags are kept.
+  // Flags denote whether the resource entry is public
+  // and under which configurations it varies.
+  const ResTable_typeSpec* type_spec;
 
-  // The configuration for which the resulting entry was defined.
-  const ResTable_config* config = nullptr;
+  // Pointer to the mmapped data where the IDMAP mappings for this type
+  // exist. May be nullptr if no IDMAP exists.
+  const IdmapEntry_header* idmap_entries;
 
-  // Stores the resulting bitmask of configuration axis with which the resource value varies.
-  uint32_t type_flags = 0u;
+  // The number of types that follow this struct.
+  // There is a type for each configuration that entries are defined for.
+  size_t type_count;
 
-  // The dynamic package ID map for the package from which this resource came from.
-  const DynamicRefTable* dynamic_ref_table = nullptr;
+  // Trick to easily access a variable number of Type structs
+  // proceeding this struct, and to ensure their alignment.
+  const ResTable_type* types[0];
 
-  // The string pool reference to the type's name. This uses a different string pool than
-  // the global string pool, but this is hidden from the caller.
-  StringPoolRef type_string_ref;
+  inline uint32_t GetFlagsForEntryIndex(uint16_t entry_index) const {
+    if (entry_index >= dtohl(type_spec->entryCount)) {
+      return 0u;
+    }
 
-  // The string pool reference to the entry's name. This uses a different string pool than
-  // the global string pool, but this is hidden from the caller.
-  StringPoolRef entry_string_ref;
+    const uint32_t* flags = reinterpret_cast<const uint32_t*>(type_spec + 1);
+    return flags[entry_index];
+  }
 };
 
-struct TypeSpec;
-class LoadedArsc;
+// TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of
+// ResTable_type pointers.
+// TypeSpecPtr is a managed pointer that knows how to delete itself.
+using TypeSpecPtr = util::unique_cptr<TypeSpec>;
 
 class LoadedPackage {
  public:
@@ -76,9 +84,6 @@
 
   ~LoadedPackage();
 
-  bool FindEntry(uint8_t type_idx, uint16_t entry_idx, const ResTable_config& config,
-                 FindEntryResult* out_entry) const;
-
   // Finds the entry with the specified type name and entry name. The names are in UTF-16 because
   // the underlying ResStringPool API expects this. For now this is acceptable, but since
   // the default policy in AAPT2 is to build UTF-8 string pools, this needs to change.
@@ -86,6 +91,12 @@
   // for patching the correct package ID to the resource ID.
   uint32_t FindEntryByName(const std::u16string& type_name, const std::u16string& entry_name) const;
 
+  static const ResTable_entry* GetEntry(const ResTable_type* type_chunk, uint16_t entry_index);
+
+  static uint32_t GetEntryOffset(const ResTable_type* type_chunk, uint16_t entry_index);
+
+  static const ResTable_entry* GetEntryFromOffset(const ResTable_type* type_chunk, uint32_t offset);
+
   // Returns the string pool where type names are stored.
   inline const ResStringPool* GetTypeStringPool() const {
     return &type_string_pool_;
@@ -135,14 +146,32 @@
   // before being inserted into the set. This may cause some equivalent locales to de-dupe.
   void CollectLocales(bool canonicalize, std::set<std::string>* out_locales) const;
 
+  // type_idx is TT - 1 from 0xPPTTEEEE.
+  inline const TypeSpec* GetTypeSpecByTypeIndex(uint8_t type_index) const {
+    // If the type IDs are offset in this package, we need to take that into account when searching
+    // for a type.
+    return type_specs_[type_index - type_id_offset_].get();
+  }
+
+  template <typename Func>
+  void ForEachTypeSpec(Func f) const {
+    for (size_t i = 0; i < type_specs_.size(); i++) {
+      const TypeSpecPtr& ptr = type_specs_[i];
+      if (ptr != nullptr) {
+        uint8_t type_id = ptr->type_spec->id;
+        if (ptr->idmap_entries != nullptr) {
+          type_id = ptr->idmap_entries->target_type_id;
+        }
+        f(ptr.get(), type_id - 1);
+      }
+    }
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
 
   LoadedPackage();
 
-  bool FindEntry(const util::unique_cptr<TypeSpec>& type_spec_ptr, uint16_t entry_idx,
-                 const ResTable_config& config, FindEntryResult* out_entry) const;
-
   ResStringPool type_string_pool_;
   ResStringPool key_string_pool_;
   std::string package_name_;
@@ -152,7 +181,7 @@
   bool system_ = false;
   bool overlay_ = false;
 
-  ByteBucketArray<util::unique_cptr<TypeSpec>> type_specs_;
+  ByteBucketArray<TypeSpecPtr> type_specs_;
   std::vector<DynamicPackageEntry> dynamic_package_map_;
 };
 
@@ -180,25 +209,20 @@
     return &global_string_pool_;
   }
 
-  // Finds the resource with ID `resid` with the best value for configuration `config`.
-  // The parameter `out_entry` will be filled with the resulting resource entry.
-  // The resource entry can be a simple entry (ResTable_entry) or a complex bag
-  // (ResTable_entry_map).
-  bool FindEntry(uint32_t resid, const ResTable_config& config, FindEntryResult* out_entry) const;
-
-  // Gets a pointer to the name of the package in `resid`, or nullptr if the package doesn't exist.
-  const LoadedPackage* GetPackageForId(uint32_t resid) const;
-
-  // Returns true if this is a system provided resource.
-  inline bool IsSystem() const {
-    return system_;
-  }
+  // Gets a pointer to the package with the specified package ID, or nullptr if no such package
+  // exists.
+  const LoadedPackage* GetPackageById(uint8_t package_id) const;
 
   // Returns a vector of LoadedPackage pointers, representing the packages in this LoadedArsc.
   inline const std::vector<std::unique_ptr<const LoadedPackage>>& GetPackages() const {
     return packages_;
   }
 
+  // Returns true if this is a system provided resource.
+  inline bool IsSystem() const {
+    return system_;
+  }
+
  private:
   DISALLOW_COPY_AND_ASSIGN(LoadedArsc);
 
diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h
new file mode 100644
index 0000000..64924f4
--- /dev/null
+++ b/libs/androidfw/include/androidfw/MutexGuard.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROIDFW_MUTEXGUARD_H
+#define ANDROIDFW_MUTEXGUARD_H
+
+#include <mutex>
+#include <type_traits>
+
+#include "android-base/macros.h"
+
+namespace android {
+
+template <typename T>
+class ScopedLock;
+
+// Owns the guarded object and protects access to it via a mutex.
+// The guarded object is inaccessible via this class.
+// The mutex is locked and the object accessed via the ScopedLock<T> class.
+//
+// NOTE: The template parameter T should not be a raw pointer, since ownership
+// is ambiguous and error-prone. Instead use an std::unique_ptr<>.
+//
+// Example use:
+//
+//   Guarded<std::string> shared_string("hello");
+//   {
+//     ScopedLock<std::string> locked_string(shared_string);
+//     *locked_string += " world";
+//   }
+//
+template <typename T>
+class Guarded {
+  static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer");
+
+ public:
+  explicit Guarded() : guarded_() {
+  }
+
+  template <typename U = T>
+  explicit Guarded(const T& guarded,
+                   typename std::enable_if<std::is_copy_constructible<U>::value>::type = void())
+      : guarded_(guarded) {
+  }
+
+  template <typename U = T>
+  explicit Guarded(T&& guarded,
+                   typename std::enable_if<std::is_move_constructible<U>::value>::type = void())
+      : guarded_(std::move(guarded)) {
+  }
+
+ private:
+  friend class ScopedLock<T>;
+
+  DISALLOW_COPY_AND_ASSIGN(Guarded);
+
+  std::mutex lock_;
+  T guarded_;
+};
+
+template <typename T>
+class ScopedLock {
+ public:
+  explicit ScopedLock(Guarded<T>& guarded) : lock_(guarded.lock_), guarded_(guarded.guarded_) {
+  }
+
+  T& operator*() {
+    return guarded_;
+  }
+
+  T* operator->() {
+    return &guarded_;
+  }
+
+  T* get() {
+    return &guarded_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedLock);
+
+  std::lock_guard<std::mutex> lock_;
+  T& guarded_;
+};
+
+}  // namespace android
+
+#endif  // ANDROIDFW_MUTEXGUARD_H
diff --git a/libs/androidfw/include/androidfw/ResourceUtils.h b/libs/androidfw/include/androidfw/ResourceUtils.h
index c2eae85..d94779b 100644
--- a/libs/androidfw/include/androidfw/ResourceUtils.h
+++ b/libs/androidfw/include/androidfw/ResourceUtils.h
@@ -28,7 +28,7 @@
                          StringPiece* out_entry);
 
 inline uint32_t fix_package_id(uint32_t resid, uint8_t package_id) {
-  return resid | (static_cast<uint32_t>(package_id) << 24);
+  return (resid & 0x00ffffffu) | (static_cast<uint32_t>(package_id) << 24);
 }
 
 inline uint8_t get_package_id(uint32_t resid) {
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 6c43a67..e2b9f00 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -26,58 +26,56 @@
 
 using ::android::base::unique_fd;
 using ::com::android::basic::R;
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::NotNull;
+using ::testing::SizeIs;
+using ::testing::StrEq;
 
 namespace android {
 
 TEST(ApkAssetsTest, LoadApk) {
   std::unique_ptr<const ApkAssets> loaded_apk =
       ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
-  ASSERT_NE(nullptr, loaded_apk);
+  ASSERT_THAT(loaded_apk, NotNull());
 
   const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
-  ASSERT_NE(nullptr, loaded_arsc);
-
-  const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
-  ASSERT_NE(nullptr, loaded_package);
-
-  std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
-  ASSERT_NE(nullptr, asset);
+  ASSERT_THAT(loaded_arsc, NotNull());
+  ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
+  ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
 }
 
 TEST(ApkAssetsTest, LoadApkFromFd) {
   const std::string path = GetTestDataPath() + "/basic/basic.apk";
   unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY));
-  ASSERT_GE(fd.get(), 0);
+  ASSERT_THAT(fd.get(), Ge(0));
 
   std::unique_ptr<const ApkAssets> loaded_apk =
       ApkAssets::LoadFromFd(std::move(fd), path, false /*system*/, false /*force_shared_lib*/);
-  ASSERT_NE(nullptr, loaded_apk);
+  ASSERT_THAT(loaded_apk, NotNull());
 
   const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
-  ASSERT_NE(nullptr, loaded_arsc);
-
-  const LoadedPackage* loaded_package = loaded_arsc->GetPackageForId(0x7f010000);
-  ASSERT_NE(nullptr, loaded_package);
-
-  std::unique_ptr<Asset> asset = loaded_apk->Open("res/layout/main.xml");
-  ASSERT_NE(nullptr, asset);
+  ASSERT_THAT(loaded_arsc, NotNull());
+  ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
+  ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
 }
 
 TEST(ApkAssetsTest, LoadApkAsSharedLibrary) {
   std::unique_ptr<const ApkAssets> loaded_apk =
       ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk");
-  ASSERT_NE(nullptr, loaded_apk);
+  ASSERT_THAT(loaded_apk, NotNull());
+
   const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
-  ASSERT_NE(nullptr, loaded_arsc);
-  ASSERT_EQ(1u, loaded_arsc->GetPackages().size());
+  ASSERT_THAT(loaded_arsc, NotNull());
+  ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u));
   EXPECT_FALSE(loaded_arsc->GetPackages()[0]->IsDynamic());
 
   loaded_apk = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk");
-  ASSERT_NE(nullptr, loaded_apk);
+  ASSERT_THAT(loaded_apk, NotNull());
 
   loaded_arsc = loaded_apk->GetLoadedArsc();
-  ASSERT_NE(nullptr, loaded_arsc);
-  ASSERT_EQ(1u, loaded_arsc->GetPackages().size());
+  ASSERT_THAT(loaded_arsc, NotNull());
+  ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u));
   EXPECT_TRUE(loaded_arsc->GetPackages()[0]->IsDynamic());
 }
 
@@ -86,19 +84,22 @@
   ResTable target_table;
   const std::string target_path = GetTestDataPath() + "/basic/basic.apk";
   ASSERT_TRUE(ReadFileFromZipToString(target_path, "resources.arsc", &contents));
-  ASSERT_EQ(NO_ERROR, target_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
+  ASSERT_THAT(target_table.add(contents.data(), contents.size(), 0, true /*copyData*/),
+              Eq(NO_ERROR));
 
   ResTable overlay_table;
   const std::string overlay_path = GetTestDataPath() + "/overlay/overlay.apk";
   ASSERT_TRUE(ReadFileFromZipToString(overlay_path, "resources.arsc", &contents));
-  ASSERT_EQ(NO_ERROR, overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/));
+  ASSERT_THAT(overlay_table.add(contents.data(), contents.size(), 0, true /*copyData*/),
+              Eq(NO_ERROR));
 
   util::unique_cptr<void> idmap_data;
   void* temp_data;
   size_t idmap_len;
 
-  ASSERT_EQ(NO_ERROR, target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(),
-                                               overlay_path.c_str(), &temp_data, &idmap_len));
+  ASSERT_THAT(target_table.createIdmap(overlay_table, 0u, 0u, target_path.c_str(),
+                                       overlay_path.c_str(), &temp_data, &idmap_len),
+              Eq(NO_ERROR));
   idmap_data.reset(temp_data);
 
   TemporaryFile tf;
@@ -108,37 +109,30 @@
   // Open something so that the destructor of TemporaryFile closes a valid fd.
   tf.fd = open("/dev/null", O_WRONLY);
 
-  std::unique_ptr<const ApkAssets> loaded_overlay_apk = ApkAssets::LoadOverlay(tf.path);
-  ASSERT_NE(nullptr, loaded_overlay_apk);
+  ASSERT_THAT(ApkAssets::LoadOverlay(tf.path), NotNull());
 }
 
 TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
   std::unique_ptr<const ApkAssets> loaded_apk =
       ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
-  ASSERT_NE(nullptr, loaded_apk);
+  ASSERT_THAT(loaded_apk, NotNull());
 
-  {
-    std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
-    ASSERT_NE(nullptr, assets);
-  }
+  { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
 
-  {
-    std::unique_ptr<Asset> assets = loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER);
-    ASSERT_NE(nullptr, assets);
-  }
+  { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
 }
 
 TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
   std::unique_ptr<const ApkAssets> loaded_apk =
       ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
-  ASSERT_NE(nullptr, loaded_apk);
+  ASSERT_THAT(loaded_apk, NotNull());
 
   auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN);
-  ASSERT_NE(nullptr, asset);
+  ASSERT_THAT(asset, NotNull());
 
   off64_t start, length;
   unique_fd fd(asset->openFileDescriptor(&start, &length));
-  EXPECT_GE(fd.get(), 0);
+  ASSERT_THAT(fd.get(), Ge(0));
 
   lseek64(fd.get(), start, SEEK_SET);
 
@@ -146,7 +140,7 @@
   buffer.resize(length);
   ASSERT_TRUE(base::ReadFully(fd.get(), &*buffer.begin(), length));
 
-  EXPECT_EQ("This should be uncompressed.\n\n", buffer);
+  EXPECT_THAT(buffer, StrEq("This should be uncompressed.\n\n"));
 }
 
 }  // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 85e8f25..437e147 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -81,17 +81,18 @@
 }
 BENCHMARK(BM_AssetManagerLoadFrameworkAssetsOld);
 
-static void BM_AssetManagerGetResource(benchmark::State& state) {
-  GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
-                       basic::R::integer::number1, state);
+static void BM_AssetManagerGetResource(benchmark::State& state, uint32_t resid) {
+  GetResourceBenchmark({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid, state);
 }
-BENCHMARK(BM_AssetManagerGetResource);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResource, number1, basic::R::integer::number1);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResource, deep_ref, basic::R::integer::deep_ref);
 
-static void BM_AssetManagerGetResourceOld(benchmark::State& state) {
-  GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/,
-                          basic::R::integer::number1, state);
+static void BM_AssetManagerGetResourceOld(benchmark::State& state, uint32_t resid) {
+  GetResourceBenchmarkOld({GetTestDataPath() + "/basic/basic.apk"}, nullptr /*config*/, resid,
+                          state);
 }
-BENCHMARK(BM_AssetManagerGetResourceOld);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, number1, basic::R::integer::number1);
+BENCHMARK_CAPTURE(BM_AssetManagerGetResourceOld, deep_ref, basic::R::integer::deep_ref);
 
 static void BM_AssetManagerGetLibraryResource(benchmark::State& state) {
   GetResourceBenchmark(
@@ -196,7 +197,7 @@
 static void BM_AssetManagerGetResourceLocalesOld(benchmark::State& state) {
   AssetManager assets;
   if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/,
-                           false /*isSystemAssets*/)) {
+                           true /*isSystemAssets*/)) {
     state.SkipWithError("Failed to load assets");
     return;
   }
@@ -211,4 +212,44 @@
 }
 BENCHMARK(BM_AssetManagerGetResourceLocalesOld);
 
+static void BM_AssetManagerSetConfigurationFramework(benchmark::State& state) {
+  std::unique_ptr<const ApkAssets> apk = ApkAssets::Load(kFrameworkPath);
+  if (apk == nullptr) {
+    state.SkipWithError("Failed to load assets");
+    return;
+  }
+
+  AssetManager2 assets;
+  assets.SetApkAssets({apk.get()});
+
+  ResTable_config config;
+  memset(&config, 0, sizeof(config));
+
+  while (state.KeepRunning()) {
+    config.sdkVersion = ~config.sdkVersion;
+    assets.SetConfiguration(config);
+  }
+}
+BENCHMARK(BM_AssetManagerSetConfigurationFramework);
+
+static void BM_AssetManagerSetConfigurationFrameworkOld(benchmark::State& state) {
+  AssetManager assets;
+  if (!assets.addAssetPath(String8(kFrameworkPath), nullptr /*cookie*/, false /*appAsLib*/,
+                           true /*isSystemAssets*/)) {
+    state.SkipWithError("Failed to load assets");
+    return;
+  }
+
+  const ResTable& table = assets.getResources(true);
+
+  ResTable_config config;
+  memset(&config, 0, sizeof(config));
+
+  while (state.KeepRunning()) {
+    config.sdkVersion = ~config.sdkVersion;
+    assets.setConfiguration(config);
+  }
+}
+BENCHMARK(BM_AssetManagerSetConfigurationFrameworkOld);
+
 }  // namespace android
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index 92462a6..eaf79cb 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -59,7 +59,7 @@
     libclient_assets_ = ApkAssets::Load(GetTestDataPath() + "/libclient/libclient.apk");
     ASSERT_NE(nullptr, libclient_assets_);
 
-    appaslib_assets_ = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk");
+    appaslib_assets_ = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk");
     ASSERT_NE(nullptr, appaslib_assets_);
 
     system_assets_ = ApkAssets::Load(GetTestDataPath() + "/system/system.apk", true /*system*/);
@@ -233,6 +233,25 @@
   assetmanager.SetApkAssets(
       {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
 
+  const ResolvedBag* bag = assetmanager.GetBag(fix_package_id(lib_one::R::style::Theme, 0x03));
+  ASSERT_NE(nullptr, bag);
+  ASSERT_GE(bag->entry_count, 2u);
+
+  // First two attributes come from lib_one.
+  EXPECT_EQ(1, bag->entries[0].cookie);
+  EXPECT_EQ(0x03, get_package_id(bag->entries[0].key));
+  EXPECT_EQ(1, bag->entries[1].cookie);
+  EXPECT_EQ(0x03, get_package_id(bag->entries[1].key));
+}
+
+TEST_F(AssetManager2Test, FindsStyleResourceWithParentFromSharedLibrary) {
+  AssetManager2 assetmanager;
+
+  // libclient is built with lib_one and then lib_two in order.
+  // Reverse the order to test that proper package ID re-assignment is happening.
+  assetmanager.SetApkAssets(
+      {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
   const ResolvedBag* bag = assetmanager.GetBag(libclient::R::style::Theme);
   ASSERT_NE(nullptr, bag);
   ASSERT_GE(bag->entry_count, 2u);
diff --git a/libs/androidfw/tests/AttributeResolution_bench.cpp b/libs/androidfw/tests/AttributeResolution_bench.cpp
new file mode 100644
index 0000000..fa300c5
--- /dev/null
+++ b/libs/androidfw/tests/AttributeResolution_bench.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "benchmark/benchmark.h"
+
+//#include "android-base/stringprintf.h"
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/AttributeResolution.h"
+#include "androidfw/ResourceTypes.h"
+
+#include "BenchmarkHelpers.h"
+#include "data/basic/R.h"
+#include "data/styles/R.h"
+
+namespace app = com::android::app;
+namespace basic = com::android::basic;
+
+namespace android {
+
+constexpr const static char* kFrameworkPath = "/system/framework/framework-res.apk";
+constexpr const static uint32_t Theme_Material_Light = 0x01030237u;
+
+static void BM_ApplyStyle(benchmark::State& state) {
+  std::unique_ptr<const ApkAssets> styles_apk =
+      ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+  if (styles_apk == nullptr) {
+    state.SkipWithError("failed to load assets");
+    return;
+  }
+
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({styles_apk.get()});
+
+  std::unique_ptr<Asset> asset =
+      assetmanager.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER);
+  if (asset == nullptr) {
+    state.SkipWithError("failed to load layout");
+    return;
+  }
+
+  ResXMLTree xml_tree;
+  if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) {
+    state.SkipWithError("corrupt xml layout");
+    return;
+  }
+
+  // Skip to the first tag.
+  while (xml_tree.next() != ResXMLParser::START_TAG) {
+  }
+
+  std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+  theme->ApplyStyle(app::R::style::StyleTwo);
+
+  std::array<uint32_t, 6> attrs{{app::R::attr::attr_one, app::R::attr::attr_two,
+                                 app::R::attr::attr_three, app::R::attr::attr_four,
+                                 app::R::attr::attr_five, app::R::attr::attr_empty}};
+  std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
+  std::array<uint32_t, attrs.size() + 1> indices;
+
+  while (state.KeepRunning()) {
+    ApplyStyle(theme.get(), &xml_tree, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(),
+               attrs.size(), values.data(), indices.data());
+  }
+}
+BENCHMARK(BM_ApplyStyle);
+
+static void BM_ApplyStyleFramework(benchmark::State& state) {
+  std::unique_ptr<const ApkAssets> framework_apk = ApkAssets::Load(kFrameworkPath);
+  if (framework_apk == nullptr) {
+    state.SkipWithError("failed to load framework assets");
+    return;
+  }
+
+  std::unique_ptr<const ApkAssets> basic_apk =
+      ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
+  if (basic_apk == nullptr) {
+    state.SkipWithError("failed to load assets");
+    return;
+  }
+
+  AssetManager2 assetmanager;
+  assetmanager.SetApkAssets({framework_apk.get(), basic_apk.get()});
+
+  ResTable_config device_config;
+  memset(&device_config, 0, sizeof(device_config));
+  device_config.language[0] = 'e';
+  device_config.language[1] = 'n';
+  device_config.country[0] = 'U';
+  device_config.country[1] = 'S';
+  device_config.orientation = ResTable_config::ORIENTATION_PORT;
+  device_config.smallestScreenWidthDp = 700;
+  device_config.screenWidthDp = 700;
+  device_config.screenHeightDp = 1024;
+  device_config.sdkVersion = 27;
+
+  Res_value value;
+  ResTable_config config;
+  uint32_t flags = 0u;
+  ApkAssetsCookie cookie =
+      assetmanager.GetResource(basic::R::layout::layoutt, false /*may_be_bag*/,
+                               0u /*density_override*/, &value, &config, &flags);
+  if (cookie == kInvalidCookie) {
+    state.SkipWithError("failed to find R.layout.layout");
+    return;
+  }
+
+  size_t len = 0u;
+  const char* layout_path =
+      assetmanager.GetStringPoolForCookie(cookie)->string8At(value.data, &len);
+  if (layout_path == nullptr || len == 0u) {
+    state.SkipWithError("failed to lookup layout path");
+    return;
+  }
+
+  std::unique_ptr<Asset> asset = assetmanager.OpenNonAsset(
+      StringPiece(layout_path, len).to_string(), cookie, Asset::ACCESS_BUFFER);
+  if (asset == nullptr) {
+    state.SkipWithError("failed to load layout");
+    return;
+  }
+
+  ResXMLTree xml_tree;
+  if (xml_tree.setTo(asset->getBuffer(true), asset->getLength(), false /*copyData*/) != NO_ERROR) {
+    state.SkipWithError("corrupt xml layout");
+    return;
+  }
+
+  // Skip to the first tag.
+  while (xml_tree.next() != ResXMLParser::START_TAG) {
+  }
+
+  std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+  theme->ApplyStyle(Theme_Material_Light);
+
+  std::array<uint32_t, 92> attrs{
+      {0x0101000e, 0x01010034, 0x01010095, 0x01010096, 0x01010097, 0x01010098, 0x01010099,
+       0x0101009a, 0x0101009b, 0x010100ab, 0x010100af, 0x010100b0, 0x010100b1, 0x0101011f,
+       0x01010120, 0x0101013f, 0x01010140, 0x0101014e, 0x0101014f, 0x01010150, 0x01010151,
+       0x01010152, 0x01010153, 0x01010154, 0x01010155, 0x01010156, 0x01010157, 0x01010158,
+       0x01010159, 0x0101015a, 0x0101015b, 0x0101015c, 0x0101015d, 0x0101015e, 0x0101015f,
+       0x01010160, 0x01010161, 0x01010162, 0x01010163, 0x01010164, 0x01010165, 0x01010166,
+       0x01010167, 0x01010168, 0x01010169, 0x0101016a, 0x0101016b, 0x0101016c, 0x0101016d,
+       0x0101016e, 0x0101016f, 0x01010170, 0x01010171, 0x01010217, 0x01010218, 0x0101021d,
+       0x01010220, 0x01010223, 0x01010224, 0x01010264, 0x01010265, 0x01010266, 0x010102c5,
+       0x010102c6, 0x010102c7, 0x01010314, 0x01010315, 0x01010316, 0x0101035e, 0x0101035f,
+       0x01010362, 0x01010374, 0x0101038c, 0x01010392, 0x01010393, 0x010103ac, 0x0101045d,
+       0x010104b6, 0x010104b7, 0x010104d6, 0x010104d7, 0x010104dd, 0x010104de, 0x010104df,
+       0x01010535, 0x01010536, 0x01010537, 0x01010538, 0x01010546, 0x01010567, 0x011100c9,
+       0x011100ca}};
+
+  std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
+  std::array<uint32_t, attrs.size() + 1> indices;
+  while (state.KeepRunning()) {
+    ApplyStyle(theme.get(), &xml_tree, 0x01010084u /*def_style_attr*/, 0u /*def_style_res*/,
+               attrs.data(), attrs.size(), values.data(), indices.data());
+  }
+}
+BENCHMARK(BM_ApplyStyleFramework);
+
+}  // namespace android
diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp
index 2d73ce8..c8dbe20 100644
--- a/libs/androidfw/tests/AttributeResolution_test.cpp
+++ b/libs/androidfw/tests/AttributeResolution_test.cpp
@@ -21,6 +21,8 @@
 #include "android-base/file.h"
 #include "android-base/logging.h"
 #include "android-base/macros.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ResourceUtils.h"
 
 #include "TestHelpers.h"
 #include "data/styles/R.h"
@@ -32,15 +34,14 @@
 class AttributeResolutionTest : public ::testing::Test {
  public:
   virtual void SetUp() override {
-    std::string contents;
-    ASSERT_TRUE(ReadFileFromZipToString(
-        GetTestDataPath() + "/styles/styles.apk", "resources.arsc", &contents));
-    ASSERT_EQ(NO_ERROR, table_.add(contents.data(), contents.size(),
-                                   1 /*cookie*/, true /*copyData*/));
+    styles_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");
+    ASSERT_NE(nullptr, styles_assets_);
+    assetmanager_.SetApkAssets({styles_assets_.get()});
   }
 
  protected:
-  ResTable table_;
+  std::unique_ptr<const ApkAssets> styles_assets_;
+  AssetManager2 assetmanager_;
 };
 
 class AttributeResolutionXmlTest : public AttributeResolutionTest {
@@ -48,13 +49,12 @@
   virtual void SetUp() override {
     AttributeResolutionTest::SetUp();
 
-    std::string contents;
-    ASSERT_TRUE(
-        ReadFileFromZipToString(GetTestDataPath() + "/styles/styles.apk",
-                                "res/layout/layout.xml", &contents));
+    std::unique_ptr<Asset> asset =
+        assetmanager_.OpenNonAsset("res/layout/layout.xml", Asset::ACCESS_BUFFER);
+    ASSERT_NE(nullptr, asset);
 
-    ASSERT_EQ(NO_ERROR, xml_parser_.setTo(contents.data(), contents.size(),
-                                          true /*copyData*/));
+    ASSERT_EQ(NO_ERROR,
+              xml_parser_.setTo(asset->getBuffer(true), asset->getLength(), true /*copyData*/));
 
     // Skip to the first tag.
     while (xml_parser_.next() != ResXMLParser::START_TAG) {
@@ -65,15 +65,50 @@
   ResXMLTree xml_parser_;
 };
 
+TEST(AttributeResolutionLibraryTest, ApplyStyleWithDefaultStyleResId) {
+  AssetManager2 assetmanager;
+  auto apk_assets = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/styles/styles.apk");
+  ASSERT_NE(nullptr, apk_assets);
+  assetmanager.SetApkAssets({apk_assets.get()});
+
+  std::unique_ptr<Theme> theme = assetmanager.NewTheme();
+
+  std::array<uint32_t, 2> attrs{
+      {fix_package_id(R::attr::attr_one, 0x02), fix_package_id(R::attr::attr_two, 0x02)}};
+  std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
+  std::array<uint32_t, attrs.size() + 1> indices;
+  ApplyStyle(theme.get(), nullptr /*xml_parser*/, 0u /*def_style_attr*/,
+             fix_package_id(R::style::StyleOne, 0x02), attrs.data(), attrs.size(), values.data(),
+             indices.data());
+
+  const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC;
+
+  const uint32_t* values_cursor = values.data();
+  EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]);
+  EXPECT_EQ(1u, values_cursor[STYLE_DATA]);
+  EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+  EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+  EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+  EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+
+  values_cursor += STYLE_NUM_ENTRIES;
+  EXPECT_EQ(Res_value::TYPE_INT_DEC, values_cursor[STYLE_TYPE]);
+  EXPECT_EQ(2u, values_cursor[STYLE_DATA]);
+  EXPECT_EQ(0u, values_cursor[STYLE_RESOURCE_ID]);
+  EXPECT_EQ(1u, values_cursor[STYLE_ASSET_COOKIE]);
+  EXPECT_EQ(0u, values_cursor[STYLE_DENSITY]);
+  EXPECT_EQ(public_flag, values_cursor[STYLE_CHANGING_CONFIGURATIONS]);
+}
+
 TEST_F(AttributeResolutionTest, Theme) {
-  ResTable::Theme theme(table_);
-  ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo));
+  std::unique_ptr<Theme> theme = assetmanager_.NewTheme();
+  ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo));
 
   std::array<uint32_t, 5> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
                                  R::attr::attr_four, R::attr::attr_empty}};
   std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
 
-  ASSERT_TRUE(ResolveAttrs(&theme, 0 /*def_style_attr*/, 0 /*def_style_res*/,
+  ASSERT_TRUE(ResolveAttrs(theme.get(), 0u /*def_style_attr*/, 0u /*def_style_res*/,
                            nullptr /*src_values*/, 0 /*src_values_length*/, attrs.data(),
                            attrs.size(), values.data(), nullptr /*out_indices*/));
 
@@ -126,8 +161,8 @@
                                  R::attr::attr_four, R::attr::attr_empty}};
   std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
 
-  ASSERT_TRUE(RetrieveAttributes(&table_, &xml_parser_, attrs.data(), attrs.size(), values.data(),
-                                 nullptr /*out_indices*/));
+  ASSERT_TRUE(RetrieveAttributes(&assetmanager_, &xml_parser_, attrs.data(), attrs.size(),
+                                 values.data(), nullptr /*out_indices*/));
 
   uint32_t* values_cursor = values.data();
   EXPECT_EQ(Res_value::TYPE_NULL, values_cursor[STYLE_TYPE]);
@@ -171,15 +206,15 @@
 }
 
 TEST_F(AttributeResolutionXmlTest, ThemeAndXmlParser) {
-  ResTable::Theme theme(table_);
-  ASSERT_EQ(NO_ERROR, theme.applyStyle(R::style::StyleTwo));
+  std::unique_ptr<Theme> theme = assetmanager_.NewTheme();
+  ASSERT_TRUE(theme->ApplyStyle(R::style::StyleTwo));
 
   std::array<uint32_t, 6> attrs{{R::attr::attr_one, R::attr::attr_two, R::attr::attr_three,
                                  R::attr::attr_four, R::attr::attr_five, R::attr::attr_empty}};
   std::array<uint32_t, attrs.size() * STYLE_NUM_ENTRIES> values;
   std::array<uint32_t, attrs.size() + 1> indices;
 
-  ApplyStyle(&theme, &xml_parser_, 0 /*def_style_attr*/, 0 /*def_style_res*/, attrs.data(),
+  ApplyStyle(theme.get(), &xml_parser_, 0u /*def_style_attr*/, 0u /*def_style_res*/, attrs.data(),
              attrs.size(), values.data(), indices.data());
 
   const uint32_t public_flag = ResTable_typeSpec::SPEC_PUBLIC;
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index 7149bee..faddfe5 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -33,19 +33,21 @@
     }
   }
 
+  // Make sure to force creation of the ResTable first, or else the configuration doesn't get set.
+  const ResTable& table = assetmanager.getResources(true);
   if (config != nullptr) {
     assetmanager.setConfiguration(*config);
   }
 
-  const ResTable& table = assetmanager.getResources(true);
-
   Res_value value;
   ResTable_config selected_config;
   uint32_t flags;
+  uint32_t last_ref = 0u;
 
   while (state.KeepRunning()) {
-    table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags,
-                      &selected_config);
+    ssize_t block = table.getResource(resid, &value, false /*may_be_bag*/, 0u /*density*/, &flags,
+                                      &selected_config);
+    table.resolveReference(&value, block, &last_ref, &flags, &selected_config);
   }
 }
 
@@ -72,10 +74,12 @@
   Res_value value;
   ResTable_config selected_config;
   uint32_t flags;
+  uint32_t last_id = 0u;
 
   while (state.KeepRunning()) {
-    assetmanager.GetResource(resid, false /* may_be_bag */, 0u /* density_override */, &value,
-                             &selected_config, &flags);
+    ApkAssetsCookie cookie = assetmanager.GetResource(
+        resid, false /* may_be_bag */, 0u /* density_override */, &value, &selected_config, &flags);
+    assetmanager.ResolveReference(cookie, &value, &selected_config, &flags, &last_id);
   }
 }
 
diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp
index 37ddafb..bedebd6 100644
--- a/libs/androidfw/tests/LoadedArsc_test.cpp
+++ b/libs/androidfw/tests/LoadedArsc_test.cpp
@@ -16,6 +16,8 @@
 
 #include "androidfw/LoadedArsc.h"
 
+#include "androidfw/ResourceUtils.h"
+
 #include "TestHelpers.h"
 #include "data/basic/R.h"
 #include "data/libclient/R.h"
@@ -27,6 +29,13 @@
 namespace libclient = com::android::libclient;
 namespace sparse = com::android::sparse;
 
+using ::testing::Eq;
+using ::testing::Ge;
+using ::testing::IsNull;
+using ::testing::NotNull;
+using ::testing::SizeIs;
+using ::testing::StrEq;
+
 namespace android {
 
 TEST(LoadedArscTest, LoadSinglePackageArsc) {
@@ -35,39 +44,24 @@
                                       &contents));
 
   std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
-  ASSERT_NE(nullptr, loaded_arsc);
+  ASSERT_THAT(loaded_arsc, NotNull());
 
-  const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages();
-  ASSERT_EQ(1u, packages.size());
-  EXPECT_EQ(std::string("com.android.app"), packages[0]->GetPackageName());
-  EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+  const LoadedPackage* package =
+      loaded_arsc->GetPackageById(get_package_id(app::R::string::string_one));
+  ASSERT_THAT(package, NotNull());
+  EXPECT_THAT(package->GetPackageName(), StrEq("com.android.app"));
+  EXPECT_THAT(package->GetPackageId(), Eq(0x7f));
 
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 24;
+  const uint8_t type_index = get_type_id(app::R::string::string_one) - 1;
+  const uint16_t entry_index = get_entry_id(app::R::string::string_one);
 
-  FindEntryResult entry;
+  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+  ASSERT_THAT(type_spec, NotNull());
+  ASSERT_THAT(type_spec->type_count, Ge(1u));
 
-  ASSERT_TRUE(loaded_arsc->FindEntry(app::R::string::string_one, config, &entry));
-  ASSERT_NE(nullptr, entry.entry);
-}
-
-TEST(LoadedArscTest, FindDefaultEntry) {
-  std::string contents;
-  ASSERT_TRUE(
-      ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
-
-  std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
-  ASSERT_NE(nullptr, loaded_arsc);
-
-  ResTable_config desired_config;
-  memset(&desired_config, 0, sizeof(desired_config));
-  desired_config.language[0] = 'd';
-  desired_config.language[1] = 'e';
-
-  FindEntryResult entry;
-  ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test1, desired_config, &entry));
-  ASSERT_NE(nullptr, entry.entry);
+  const ResTable_type* type = type_spec->types[0];
+  ASSERT_THAT(type, NotNull());
+  ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
 }
 
 TEST(LoadedArscTest, LoadSparseEntryApp) {
@@ -76,15 +70,22 @@
                                       &contents));
 
   std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
-  ASSERT_NE(nullptr, loaded_arsc);
+  ASSERT_THAT(loaded_arsc, NotNull());
 
-  ResTable_config config;
-  memset(&config, 0, sizeof(config));
-  config.sdkVersion = 26;
+  const LoadedPackage* package =
+      loaded_arsc->GetPackageById(get_package_id(sparse::R::integer::foo_9));
+  ASSERT_THAT(package, NotNull());
 
-  FindEntryResult entry;
-  ASSERT_TRUE(loaded_arsc->FindEntry(sparse::R::integer::foo_9, config, &entry));
-  ASSERT_NE(nullptr, entry.entry);
+  const uint8_t type_index = get_type_id(sparse::R::integer::foo_9) - 1;
+  const uint16_t entry_index = get_entry_id(sparse::R::integer::foo_9);
+
+  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+  ASSERT_THAT(type_spec, NotNull());
+  ASSERT_THAT(type_spec->type_count, Ge(1u));
+
+  const ResTable_type* type = type_spec->types[0];
+  ASSERT_THAT(type, NotNull());
+  ASSERT_THAT(LoadedPackage::GetEntry(type, entry_index), NotNull());
 }
 
 TEST(LoadedArscTest, LoadSharedLibrary) {
@@ -93,14 +94,13 @@
                                       &contents));
 
   std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
-  ASSERT_NE(nullptr, loaded_arsc);
+  ASSERT_THAT(loaded_arsc, NotNull());
 
   const auto& packages = loaded_arsc->GetPackages();
-  ASSERT_EQ(1u, packages.size());
-
+  ASSERT_THAT(packages, SizeIs(1u));
   EXPECT_TRUE(packages[0]->IsDynamic());
-  EXPECT_EQ(std::string("com.android.lib_one"), packages[0]->GetPackageName());
-  EXPECT_EQ(0, packages[0]->GetPackageId());
+  EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.lib_one"));
+  EXPECT_THAT(packages[0]->GetPackageId(), Eq(0));
 
   const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap();
 
@@ -114,25 +114,23 @@
                                       "resources.arsc", &contents));
 
   std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
-  ASSERT_NE(nullptr, loaded_arsc);
+  ASSERT_THAT(loaded_arsc, NotNull());
 
   const auto& packages = loaded_arsc->GetPackages();
-  ASSERT_EQ(1u, packages.size());
-
+  ASSERT_THAT(packages, SizeIs(1u));
   EXPECT_FALSE(packages[0]->IsDynamic());
-  EXPECT_EQ(std::string("com.android.libclient"), packages[0]->GetPackageName());
-  EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+  EXPECT_THAT(packages[0]->GetPackageName(), StrEq("com.android.libclient"));
+  EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f));
 
   const auto& dynamic_pkg_map = packages[0]->GetDynamicPackageMap();
 
   // The library has two dependencies.
-  ASSERT_EQ(2u, dynamic_pkg_map.size());
+  ASSERT_THAT(dynamic_pkg_map, SizeIs(2u));
+  EXPECT_THAT(dynamic_pkg_map[0].package_name, StrEq("com.android.lib_one"));
+  EXPECT_THAT(dynamic_pkg_map[0].package_id, Eq(0x02));
 
-  EXPECT_EQ(std::string("com.android.lib_one"), dynamic_pkg_map[0].package_name);
-  EXPECT_EQ(0x02, dynamic_pkg_map[0].package_id);
-
-  EXPECT_EQ(std::string("com.android.lib_two"), dynamic_pkg_map[1].package_name);
-  EXPECT_EQ(0x03, dynamic_pkg_map[1].package_id);
+  EXPECT_THAT(dynamic_pkg_map[1].package_name, StrEq("com.android.lib_two"));
+  EXPECT_THAT(dynamic_pkg_map[1].package_id, Eq(0x03));
 }
 
 TEST(LoadedArscTest, LoadAppAsSharedLibrary) {
@@ -143,13 +141,12 @@
   std::unique_ptr<const LoadedArsc> loaded_arsc =
       LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/,
                        true /*load_as_shared_library*/);
-  ASSERT_NE(nullptr, loaded_arsc);
+  ASSERT_THAT(loaded_arsc, NotNull());
 
   const auto& packages = loaded_arsc->GetPackages();
-  ASSERT_EQ(1u, packages.size());
-
+  ASSERT_THAT(packages, SizeIs(1u));
   EXPECT_TRUE(packages[0]->IsDynamic());
-  EXPECT_EQ(0x7f, packages[0]->GetPackageId());
+  EXPECT_THAT(packages[0]->GetPackageId(), Eq(0x7f));
 }
 
 TEST(LoadedArscTest, LoadFeatureSplit) {
@@ -157,21 +154,27 @@
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/feature/feature.apk", "resources.arsc",
                                       &contents));
   std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents));
-  ASSERT_NE(nullptr, loaded_arsc);
+  ASSERT_THAT(loaded_arsc, NotNull());
 
-  ResTable_config desired_config;
-  memset(&desired_config, 0, sizeof(desired_config));
+  const LoadedPackage* package =
+      loaded_arsc->GetPackageById(get_package_id(basic::R::string::test3));
+  ASSERT_THAT(package, NotNull());
 
-  FindEntryResult entry;
-  ASSERT_TRUE(loaded_arsc->FindEntry(basic::R::string::test3, desired_config, &entry));
+  uint8_t type_index = get_type_id(basic::R::string::test3) - 1;
+  uint8_t entry_index = get_entry_id(basic::R::string::test3);
+
+  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(type_index);
+  ASSERT_THAT(type_spec, NotNull());
+  ASSERT_THAT(type_spec->type_count, Ge(1u));
+  ASSERT_THAT(type_spec->types[0], NotNull());
 
   size_t len;
-  const char16_t* type_name16 = entry.type_string_ref.string16(&len);
-  ASSERT_NE(nullptr, type_name16);
-  ASSERT_NE(0u, len);
+  const char16_t* type_name16 =
+      package->GetTypeStringPool()->stringAt(type_spec->type_spec->id - 1, &len);
+  ASSERT_THAT(type_name16, NotNull());
+  EXPECT_THAT(util::Utf16ToUtf8(StringPiece16(type_name16, len)), StrEq("string"));
 
-  std::string type_name = util::Utf16ToUtf8(StringPiece16(type_name16, len));
-  EXPECT_EQ(std::string("string"), type_name);
+  ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], entry_index), NotNull());
 }
 
 class MockLoadedIdmap : public LoadedIdmap {
@@ -199,23 +202,33 @@
 };
 
 TEST(LoadedArscTest, LoadOverlay) {
-  std::string contents, overlay_contents;
-  ASSERT_TRUE(
-      ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents));
+  std::string contents;
   ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlay/overlay.apk", "resources.arsc",
-                                      &overlay_contents));
+                                      &contents));
 
   MockLoadedIdmap loaded_idmap;
 
   std::unique_ptr<const LoadedArsc> loaded_arsc =
-      LoadedArsc::Load(StringPiece(overlay_contents), &loaded_idmap);
-  ASSERT_NE(nullptr, loaded_arsc);
+      LoadedArsc::Load(StringPiece(contents), &loaded_idmap);
+  ASSERT_THAT(loaded_arsc, NotNull());
 
-  ResTable_config desired_config;
-  memset(&desired_config, 0, sizeof(desired_config));
+  const LoadedPackage* package = loaded_arsc->GetPackageById(0x08u);
+  ASSERT_THAT(package, NotNull());
 
-  FindEntryResult entry;
-  ASSERT_TRUE(loaded_arsc->FindEntry(0x08030001u, desired_config, &entry));
+  const TypeSpec* type_spec = package->GetTypeSpecByTypeIndex(0x03u - 1);
+  ASSERT_THAT(type_spec, NotNull());
+  ASSERT_THAT(type_spec->type_count, Ge(1u));
+  ASSERT_THAT(type_spec->types[0], NotNull());
+
+  // The entry being overlaid doesn't exist at the original entry index.
+  ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0001u), IsNull());
+
+  // Since this is an overlay, the actual entry ID must be mapped.
+  ASSERT_THAT(type_spec->idmap_entries, NotNull());
+  uint16_t target_entry_id = 0u;
+  ASSERT_TRUE(LoadedIdmap::Lookup(type_spec->idmap_entries, 0x0001u, &target_entry_id));
+  ASSERT_THAT(target_entry_id, Eq(0x0u));
+  ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0000), NotNull());
 }
 
 // structs with size fields (like Res_value, ResTable_entry) should be
diff --git a/libs/androidfw/tests/TestHelpers.h b/libs/androidfw/tests/TestHelpers.h
index 43a9955..df0c642 100644
--- a/libs/androidfw/tests/TestHelpers.h
+++ b/libs/androidfw/tests/TestHelpers.h
@@ -20,6 +20,7 @@
 #include <string>
 
 #include "androidfw/ResourceTypes.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 #include "CommonHelpers.h"
diff --git a/libs/androidfw/tests/data/basic/R.h b/libs/androidfw/tests/data/basic/R.h
index 94a2a14..b7e814f 100644
--- a/libs/androidfw/tests/data/basic/R.h
+++ b/libs/androidfw/tests/data/basic/R.h
@@ -34,6 +34,7 @@
   struct layout {
     enum : uint32_t {
       main = 0x7f020000,
+      layoutt = 0x7f020001,
     };
   };
 
@@ -55,6 +56,7 @@
       number2 = 0x7f040001,
       ref1 = 0x7f040002,
       ref2 = 0x7f040003,
+      deep_ref = 0x7f040004,
 
       // From feature
       number3 = 0x80030000,
diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk
index 18ef75e..1733b6a 100644
--- a/libs/androidfw/tests/data/basic/basic.apk
+++ b/libs/androidfw/tests/data/basic/basic.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/res/layout/layout.xml b/libs/androidfw/tests/data/basic/res/layout/layout.xml
new file mode 100644
index 0000000..045ede4
--- /dev/null
+++ b/libs/androidfw/tests/data/basic/res/layout/layout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/ok"
+    android:layout_width="0sp"
+    android:layout_height="fill_parent"
+    android:layout_weight="1"
+    android:layout_marginStart="2dip"
+    android:layout_marginEnd="2dip"
+    android:textAppearance="?android:attr/textAppearanceMedium"
+    android:textStyle="bold"
+    android:text="@android:string/ok" />
\ No newline at end of file
diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml
index 6c47459..b343562 100644
--- a/libs/androidfw/tests/data/basic/res/values/values.xml
+++ b/libs/androidfw/tests/data/basic/res/values/values.xml
@@ -22,6 +22,7 @@
     <attr name="attr2" format="reference|integer" />
 
     <public type="layout" name="main" id="0x7f020000" />
+    <public type="layout" name="layout" id="0x7f020001" />
 
     <public type="string" name="test1" id="0x7f030000" />
     <string name="test1">test1</string>
@@ -43,6 +44,18 @@
     <public type="integer" name="ref2" id="0x7f040003" />
     <integer name="ref2">12000</integer>
 
+    <public type="integer" name="deep_ref" id="0x7f040004" />
+    <integer name="deep_ref">@integer/deep_ref_1</integer>
+    <integer name="deep_ref_1">@integer/deep_ref_2</integer>
+    <integer name="deep_ref_2">@integer/deep_ref_3</integer>
+    <integer name="deep_ref_3">@integer/deep_ref_4</integer>
+    <integer name="deep_ref_4">@integer/deep_ref_5</integer>
+    <integer name="deep_ref_5">@integer/deep_ref_6</integer>
+    <integer name="deep_ref_6">@integer/deep_ref_7</integer>
+    <integer name="deep_ref_7">@integer/deep_ref_8</integer>
+    <integer name="deep_ref_8">@integer/deep_ref_9</integer>
+    <integer name="deep_ref_9">100</integer>
+
     <public type="style" name="Theme1" id="0x7f050000" />
     <style name="Theme1">
         <item name="com.android.basic:attr1">100</item>
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index b2edd33..9e0d10d 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -187,11 +187,6 @@
 static inline SkCanvas::SaveLayerFlags layerFlags(SaveFlags::Flags flags) {
     SkCanvas::SaveLayerFlags layerFlags = 0;
 
-    // We intentionally ignore the SaveFlags::HasAlphaLayer and
-    // SkCanvas::kIsOpaque_SaveLayerFlag flags because HWUI ignores it
-    // and our Android client may use it incorrectly.
-    // In Skia, this flag is purely for performance optimization.
-
     if (!(flags & SaveFlags::ClipToLayer)) {
         layerFlags |= SkCanvas::kDontClipToLayer_Legacy_SaveLayerFlag;
     }
diff --git a/libs/hwui/SkiaCanvasProxy.cpp b/libs/hwui/SkiaCanvasProxy.cpp
index 06e2d6c..fc009d8 100644
--- a/libs/hwui/SkiaCanvasProxy.cpp
+++ b/libs/hwui/SkiaCanvasProxy.cpp
@@ -204,10 +204,6 @@
         saveFlags |= SaveFlags::ClipToLayer;
     }
 
-    if (!(layerFlags & SkCanvas::kIsOpaque_SaveLayerFlag)) {
-        saveFlags |= SaveFlags::HasAlphaLayer;
-    }
-
     return saveFlags;
 }
 
diff --git a/libs/hwui/hwui/Typeface.cpp b/libs/hwui/hwui/Typeface.cpp
index 091b526..dca9ef5 100644
--- a/libs/hwui/hwui/Typeface.cpp
+++ b/libs/hwui/hwui/Typeface.cpp
@@ -132,8 +132,8 @@
         bool italicFromFont;
 
         const minikin::FontStyle defaultStyle;
-        const minikin::MinikinFont* mf =
-                families.empty() ? nullptr : families[0]->getClosestMatch(defaultStyle).font;
+        const minikin::MinikinFont* mf = families.empty() ? nullptr
+                : families[0]->getClosestMatch(defaultStyle).font->typeface().get();
         if (mf != nullptr) {
             SkTypeface* skTypeface = reinterpret_cast<const MinikinFontSkia*>(mf)->GetSkTypeface();
             const SkFontStyle& style = skTypeface->fontStyle();
@@ -183,7 +183,7 @@
     std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
             std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>());
     std::vector<minikin::Font> fonts;
-    fonts.push_back(minikin::Font(std::move(font), minikin::FontStyle()));
+    fonts.push_back(minikin::Font::Builder(font).build());
 
     std::shared_ptr<minikin::FontCollection> collection = std::make_shared<minikin::FontCollection>(
             std::make_shared<minikin::FontFamily>(std::move(fonts)));
diff --git a/libs/hwui/tests/unit/TypefaceTests.cpp b/libs/hwui/tests/unit/TypefaceTests.cpp
index 2232c25..e424a26 100644
--- a/libs/hwui/tests/unit/TypefaceTests.cpp
+++ b/libs/hwui/tests/unit/TypefaceTests.cpp
@@ -57,7 +57,7 @@
     std::shared_ptr<minikin::MinikinFont> font = std::make_shared<MinikinFontSkia>(
             std::move(typeface), data, st.st_size, 0, std::vector<minikin::FontVariation>());
     std::vector<minikin::Font> fonts;
-    fonts.push_back(minikin::Font(std::move(font), minikin::FontStyle()));
+    fonts.push_back(minikin::Font::Builder(font).build());
     return std::make_shared<minikin::FontFamily>(std::move(fonts));
 }
 
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 7e5f581..fe2f64f 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -127,11 +127,11 @@
         "liblzma",
         "libmedia",
         "libmedia_helper",
-        "libmedia_player2",
         "libmedia_player2_util",
         "libmediadrm",
         "libmediaextractor",
         "libmediametrics",
+        "libmediaplayer2",
         "libmediautils",
         "libnativehelper",
         "libnetd_client",
diff --git a/media/jni/android_media_MediaPlayer2.cpp b/media/jni/android_media_MediaPlayer2.cpp
index 27eaed0..0eb98f3 100644
--- a/media/jni/android_media_MediaPlayer2.cpp
+++ b/media/jni/android_media_MediaPlayer2.cpp
@@ -21,15 +21,14 @@
 
 #include <sys/stat.h>
 
-#include <media/mediaplayer2.h>
 #include <media/AudioResamplerPublic.h>
 #include <media/DataSourceDesc.h>
 #include <media/MediaHTTPService.h>
-#include <media/MediaPlayer2Interface.h>
 #include <media/MediaAnalyticsItem.h>
 #include <media/NdkWrapper.h>
 #include <media/stagefright/Utils.h>
 #include <media/stagefright/foundation/ByteUtils.h>  // for FOURCC definition
+#include <mediaplayer2/mediaplayer2.h>
 #include <stdio.h>
 #include <assert.h>
 #include <limits.h>
diff --git a/native/android/asset_manager.cpp b/native/android/asset_manager.cpp
index 98e9a42..e70d5ea 100644
--- a/native/android/asset_manager.cpp
+++ b/native/android/asset_manager.cpp
@@ -18,9 +18,11 @@
 #include <utils/Log.h>
 
 #include <android/asset_manager_jni.h>
+#include <android_runtime/android_util_AssetManager.h>
 #include <androidfw/Asset.h>
 #include <androidfw/AssetDir.h>
 #include <androidfw/AssetManager.h>
+#include <androidfw/AssetManager2.h>
 #include <utils/threads.h>
 
 #include "jni.h"
@@ -35,21 +37,20 @@
 
 // -----
 struct AAssetDir {
-    AssetDir* mAssetDir;
+    std::unique_ptr<AssetDir> mAssetDir;
     size_t mCurFileIndex;
     String8 mCachedFileName;
 
-    explicit AAssetDir(AssetDir* dir) : mAssetDir(dir), mCurFileIndex(0) { }
-    ~AAssetDir() { delete mAssetDir; }
+    explicit AAssetDir(std::unique_ptr<AssetDir> dir) :
+        mAssetDir(std::move(dir)), mCurFileIndex(0) { }
 };
 
 
 // -----
 struct AAsset {
-    Asset* mAsset;
+    std::unique_ptr<Asset> mAsset;
 
-    explicit AAsset(Asset* asset) : mAsset(asset) { }
-    ~AAsset() { delete mAsset; }
+    explicit AAsset(std::unique_ptr<Asset> asset) : mAsset(std::move(asset)) { }
 };
 
 // -------------------- Public native C API --------------------
@@ -104,19 +105,18 @@
         return NULL;
     }
 
-    AssetManager* mgr = static_cast<AssetManager*>(amgr);
-    Asset* asset = mgr->open(filename, amMode);
-    if (asset == NULL) {
-        return NULL;
+    ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr));
+    std::unique_ptr<Asset> asset = locked_mgr->Open(filename, amMode);
+    if (asset == nullptr) {
+        return nullptr;
     }
-
-    return new AAsset(asset);
+    return new AAsset(std::move(asset));
 }
 
 AAssetDir* AAssetManager_openDir(AAssetManager* amgr, const char* dirName)
 {
-    AssetManager* mgr = static_cast<AssetManager*>(amgr);
-    return new AAssetDir(mgr->openDir(dirName));
+    ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(amgr));
+    return new AAssetDir(locked_mgr->OpenDir(dirName));
 }
 
 /**
diff --git a/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
index 72be35b..15dd64b 100644
--- a/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
+++ b/packages/PrintSpooler/tests/outofprocess/AndroidTest.xml
@@ -20,6 +20,8 @@
 
     <option name="test-suite-tag" value="apct" />
     <option name="test-tag" value="PrintSpoolerOutOfProcessTests" />
+    <option name="config-descriptor:metadata" key="component" value="print" />
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.printspooler.outofprocess.tests" />
         <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
diff --git a/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml b/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml
index ac56a2d..bc330c7 100644
--- a/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml
+++ b/packages/SettingsLib/res/layout/zen_mode_turn_on_dialog_container.xml
@@ -22,25 +22,40 @@
             android:fillViewport ="true"
             android:orientation="vertical">
 
-    <com.android.settingslib.notification.ZenRadioLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/zen_conditions"
+    <LinearLayout
+        android:id="@+id/dialog_container"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="8dp"
-        android:layout_marginEnd="4dp"
-        android:layout_marginStart="4dp"
-        android:paddingBottom="4dp"
-        android:orientation="horizontal">
-        <RadioGroup
-            android:id="@+id/zen_radio_buttons"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content" />
-        <LinearLayout
-            android:id="@+id/zen_radio_buttons_content"
-            android:layout_width="fill_parent"
-            android:layout_height="fill_parent"
-            android:orientation="vertical"/>
-    </com.android.settingslib.notification.ZenRadioLayout>
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <com.android.settingslib.notification.ZenRadioLayout
+            android:id="@+id/zen_conditions"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="8dp"
+            android:layout_marginEnd="4dp"
+            android:layout_marginStart="4dp"
+            android:paddingBottom="4dp"
+            android:orientation="horizontal">
+            <RadioGroup
+                android:id="@+id/zen_radio_buttons"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content" />
+            <LinearLayout
+                android:id="@+id/zen_radio_buttons_content"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:orientation="vertical"/>
+        </com.android.settingslib.notification.ZenRadioLayout>
+
+        <TextView
+            android:id="@+id/zen_alarm_warning"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="18dp"
+            android:layout_marginEnd="16dp"
+            android:textDirection="locale"
+            android:textColor="?android:attr/colorError"/>
+    </LinearLayout>
 
 </ScrollView>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 486a9bb..6ef3fac 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1039,5 +1039,13 @@
     <string name="zen_interruption_level_priority">Priority only</string>
     <!-- [CHAR LIMIT=20] Accessibility string for current zen mode and selected exit condition. A template that simply concatenates existing mode string and the current condition description.  -->
     <string name="zen_mode_and_condition"><xliff:g id="zen_mode" example="Priority interruptions only">%1$s</xliff:g>. <xliff:g id="exit_condition" example="For one hour">%2$s</xliff:g></string>
+    <!-- Warning text when an alarm might be silenced by Do Not Disturb [CHAR LIMIT=NONE] -->
+    <string name="zen_alarm_warning_indef">You won\'t hear your next alarm <xliff:g id="when" example="at 7:00 AM">%1$s</xliff:g> unless you turn this off before then</string>
+    <!-- Warning text when an alarm might be silenced by Do Not Disturb due to a time-based condition [CHAR LIMIT=NONE] -->
+    <string name="zen_alarm_warning">You won\'t hear your next alarm <xliff:g id="when" example="at 7:00 AM">%1$s</xliff:g></string>
+    <!-- Alarm template for near alarms [CHAR LIMIT=25] -->
+    <string name="alarm_template">at <xliff:g id="when" example="7:00 AM">%1$s</xliff:g></string>
+    <!-- Alarm template for far in the future alarms [CHAR LIMIT=25] -->
+    <string name="alarm_template_far">on <xliff:g id="when" example="Fri 7:00 AM">%1$s</xliff:g></string>
 
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index a20f687..1a54d6a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -1,5 +1,3 @@
-package com.android.settingslib.notification;
-
 /*
  * Copyright (C) 2018 The Android Open Source Project
  *
@@ -16,6 +14,8 @@
  * limitations under the License.
  */
 
+package com.android.settingslib.notification;
+
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AlertDialog;
@@ -28,6 +28,7 @@
 import android.service.notification.Condition;
 import android.service.notification.ZenModeConfig;
 import android.text.TextUtils;
+import android.text.format.DateFormat;
 import android.util.Log;
 import android.util.Slog;
 import android.view.LayoutInflater;
@@ -40,6 +41,7 @@
 import android.widget.ScrollView;
 import android.widget.TextView;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.policy.PhoneWindow;
@@ -48,11 +50,11 @@
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.GregorianCalendar;
+import java.util.Locale;
 import java.util.Objects;
 
 public class EnableZenModeDialog {
-
-    private static final String TAG = "QSEnableZenModeDialog";
+    private static final String TAG = "EnableZenModeDialog";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
@@ -60,25 +62,37 @@
     private static final int MAX_BUCKET_MINUTES = MINUTE_BUCKETS[MINUTE_BUCKETS.length - 1];
     private static final int DEFAULT_BUCKET_INDEX = Arrays.binarySearch(MINUTE_BUCKETS, 60);
 
-    private static final int FOREVER_CONDITION_INDEX = 0;
-    private static final int COUNTDOWN_CONDITION_INDEX = 1;
-    private static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2;
+    @VisibleForTesting
+    protected static final int FOREVER_CONDITION_INDEX = 0;
+    @VisibleForTesting
+    protected static final int COUNTDOWN_CONDITION_INDEX = 1;
+    @VisibleForTesting
+    protected static final int COUNTDOWN_ALARM_CONDITION_INDEX = 2;
 
     private static final int SECONDS_MS = 1000;
     private static final int MINUTES_MS = 60 * SECONDS_MS;
 
-    private Uri mForeverId;
+    @VisibleForTesting
+    protected Uri mForeverId;
     private int mBucketIndex = -1;
 
     private AlarmManager mAlarmManager;
     private int mUserId;
     private boolean mAttached;
 
-    private Context mContext;
+    @VisibleForTesting
+    protected Context mContext;
+    @VisibleForTesting
+    protected TextView mZenAlarmWarning;
+    @VisibleForTesting
+    protected LinearLayout mZenRadioGroupContent;
+
     private RadioGroup mZenRadioGroup;
-    private LinearLayout mZenRadioGroupContent;
     private int MAX_MANUAL_DND_OPTIONS = 3;
 
+    @VisibleForTesting
+    protected LayoutInflater mLayoutInflater;
+
     public EnableZenModeDialog(Context context) {
         mContext = context;
     }
@@ -133,32 +147,40 @@
         for (int i = 0; i < N; i++) {
             mZenRadioGroupContent.getChildAt(i).setVisibility(View.GONE);
         }
+
+        mZenAlarmWarning.setVisibility(View.GONE);
     }
 
     protected View getContentView() {
-        final LayoutInflater inflater = new PhoneWindow(mContext).getLayoutInflater();
-        View contentView = inflater.inflate(R.layout.zen_mode_turn_on_dialog_container, null);
+        if (mLayoutInflater == null) {
+            mLayoutInflater = new PhoneWindow(mContext).getLayoutInflater();
+        }
+        View contentView = mLayoutInflater.inflate(R.layout.zen_mode_turn_on_dialog_container,
+                null);
         ScrollView container = (ScrollView) contentView.findViewById(R.id.container);
 
         mZenRadioGroup = container.findViewById(R.id.zen_radio_buttons);
         mZenRadioGroupContent = container.findViewById(R.id.zen_radio_buttons_content);
+        mZenAlarmWarning = container.findViewById(R.id.zen_alarm_warning);
 
         for (int i = 0; i < MAX_MANUAL_DND_OPTIONS; i++) {
-            final View radioButton = inflater.inflate(R.layout.zen_mode_radio_button,
+            final View radioButton = mLayoutInflater.inflate(R.layout.zen_mode_radio_button,
                     mZenRadioGroup, false);
             mZenRadioGroup.addView(radioButton);
             radioButton.setId(i);
 
-            final View radioButtonContent = inflater.inflate(R.layout.zen_mode_condition,
+            final View radioButtonContent = mLayoutInflater.inflate(R.layout.zen_mode_condition,
                     mZenRadioGroupContent, false);
             radioButtonContent.setId(i + MAX_MANUAL_DND_OPTIONS);
             mZenRadioGroupContent.addView(radioButtonContent);
         }
+
         hideAllConditions();
         return contentView;
     }
 
-    private void bind(final Condition condition, final View row, final int rowId) {
+    @VisibleForTesting
+    protected void bind(final Condition condition, final View row, final int rowId) {
         if (condition == null) throw new IllegalArgumentException("condition must not be null");
         final boolean enabled = condition.state == Condition.STATE_TRUE;
         final ConditionTag tag = row.getTag() != null ? (ConditionTag) row.getTag() :
@@ -181,6 +203,7 @@
                     if (DEBUG) Log.d(TAG, "onCheckedChanged " + conditionId);
                     MetricsLogger.action(mContext,
                             MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT);
+                    updateAlarmWarningText(tag.condition);
                     announceConditionSelection(tag);
                 }
             }
@@ -190,11 +213,13 @@
         row.setVisibility(View.VISIBLE);
     }
 
-    private ConditionTag getConditionTagAt(int index) {
+    @VisibleForTesting
+    protected ConditionTag getConditionTagAt(int index) {
         return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag();
     }
 
-    private void bindConditions(Condition c) {
+    @VisibleForTesting
+    protected void bindConditions(Condition c) {
         // forever
         bind(forever(), mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX),
                 FOREVER_CONDITION_INDEX);
@@ -236,11 +261,13 @@
         return info != null ? info.getTriggerTime() : 0;
     }
 
-    private boolean isAlarm(Condition c) {
+    @VisibleForTesting
+    protected boolean isAlarm(Condition c) {
         return c != null && ZenModeConfig.isValidCountdownToAlarmConditionId(c.id);
     }
 
-    private boolean isCountdown(Condition c) {
+    @VisibleForTesting
+    protected boolean isCountdown(Condition c) {
         return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
     }
 
@@ -264,7 +291,8 @@
     }
 
     // Returns a time condition if the next alarm is within the next week.
-    private Condition getTimeUntilNextAlarmCondition() {
+    @VisibleForTesting
+    protected Condition getTimeUntilNextAlarmCondition() {
         GregorianCalendar weekRange = new GregorianCalendar();
         setToMidnight(weekRange);
         weekRange.add(Calendar.DATE, 6);
@@ -282,7 +310,8 @@
         return null;
     }
 
-    private void bindGenericCountdown() {
+    @VisibleForTesting
+    protected void bindGenericCountdown() {
         mBucketIndex = DEFAULT_BUCKET_INDEX;
         Condition countdown = ZenModeConfig.toTimeCondition(mContext,
                 MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
@@ -366,7 +395,8 @@
         }
     }
 
-    private void bindNextAlarm(Condition c) {
+    @VisibleForTesting
+    protected void bindNextAlarm(Condition c) {
         View alarmContent = mZenRadioGroupContent.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX);
         ConditionTag tag = (ConditionTag) alarmContent.getTag();
 
@@ -415,6 +445,7 @@
                     MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
         }
         bind(newCondition, row, rowId);
+        updateAlarmWarningText(tag.condition);
         tag.rb.setChecked(true);
         announceConditionSelection(tag);
     }
@@ -428,8 +459,43 @@
         }
     }
 
+    private void updateAlarmWarningText(Condition condition) {
+        String warningText = computeAlarmWarningText(condition);
+        mZenAlarmWarning.setText(warningText);
+        mZenAlarmWarning.setVisibility(warningText == null ? View.GONE : View.VISIBLE);
+    }
+
+    private String computeAlarmWarningText(Condition condition) {
+        final long now = System.currentTimeMillis();
+        final long nextAlarm = getNextAlarm();
+        if (nextAlarm < now) {
+            return null;
+        }
+        int warningRes = 0;
+        if (condition == null || isForever(condition)) {
+            warningRes = R.string.zen_alarm_warning_indef;
+        } else {
+            final long time = ZenModeConfig.tryParseCountdownConditionId(condition.id);
+            if (time > now && nextAlarm < time) {
+                warningRes = R.string.zen_alarm_warning;
+            }
+        }
+        if (warningRes == 0) {
+            return null;
+        }
+        final boolean soon = (nextAlarm - now) < 24 * 60 * 60 * 1000;
+        final boolean is24 = DateFormat.is24HourFormat(mContext, ActivityManager.getCurrentUser());
+        final String skeleton = soon ? (is24 ? "Hm" : "hma") : (is24 ? "EEEHm" : "EEEhma");
+        final String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), skeleton);
+        final CharSequence formattedTime = DateFormat.format(pattern, nextAlarm);
+        final int templateRes = soon ? R.string.alarm_template : R.string.alarm_template_far;
+        final String template = mContext.getResources().getString(templateRes, formattedTime);
+        return mContext.getResources().getString(warningRes, template);
+    }
+
     // used as the view tag on condition rows
-    private static class ConditionTag {
+    @VisibleForTesting
+    protected static class ConditionTag {
         public RadioButton rb;
         public View lines;
         public TextView line1;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java
new file mode 100644
index 0000000..777cd98
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/EnableZenModeDialogTest.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.notification;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Context;
+import android.net.Uri;
+import android.service.notification.Condition;
+import android.view.LayoutInflater;
+
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class EnableZenModeDialogTest {
+    private EnableZenModeDialog mController;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private Fragment mFragment;
+
+    private Context mShadowContext;
+    private LayoutInflater mLayoutInflater;
+    private Condition mCountdownCondition;
+    private Condition mAlarmCondition;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+        mShadowContext = RuntimeEnvironment.application;
+        when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mFragment.getContext()).thenReturn(mShadowContext);
+        mLayoutInflater = LayoutInflater.from(mShadowContext);
+
+        mController = spy(new EnableZenModeDialog(mContext));
+        mController.mContext = mContext;
+        mController.mLayoutInflater = mLayoutInflater;
+        mController.mForeverId =  Condition.newId(mContext).appendPath("forever").build();
+        when(mContext.getString(com.android.internal.R.string.zen_mode_forever))
+                .thenReturn("testSummary");
+        mController.getContentView();
+
+        // these methods use static calls to ZenModeConfig which would normally fail in robotests,
+        // so instead do nothing:
+        doNothing().when(mController).bindGenericCountdown();
+        doReturn(null).when(mController).getTimeUntilNextAlarmCondition();
+        doReturn(0L).when(mController).getNextAlarm();
+        doNothing().when(mController).bindNextAlarm(any());
+
+        // as a result of doing nothing above, must bind manually:
+        Uri alarm =  Condition.newId(mContext).appendPath("alarm").build();
+        mAlarmCondition = new Condition(alarm, "alarm", "", "", 0, 0, 0);
+        Uri countdown =  Condition.newId(mContext).appendPath("countdown").build();
+        mCountdownCondition = new Condition(countdown, "countdown", "", "", 0, 0, 0);
+        mController.bind(mCountdownCondition,
+                mController.mZenRadioGroupContent.getChildAt(
+                        EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX),
+                EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX);
+        mController.bind(mAlarmCondition,
+                mController.mZenRadioGroupContent.getChildAt(
+                        EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX),
+                EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX);
+    }
+
+    @Test
+    public void testForeverChecked() {
+        mController.bindConditions(mController.forever());
+
+        assertTrue(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
+                .isChecked());
+        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
+                .isChecked());
+        assertFalse(mController.getConditionTagAt(
+                EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+    }
+
+    @Test
+    public void testNoneChecked() {
+        mController.bindConditions(null);
+        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
+                .isChecked());
+        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
+                .isChecked());
+        assertFalse(mController.getConditionTagAt(
+                EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+    }
+
+    @Test
+    public void testAlarmChecked() {
+        doReturn(false).when(mController).isCountdown(mAlarmCondition);
+        doReturn(true).when(mController).isAlarm(mAlarmCondition);
+
+        mController.bindConditions(mAlarmCondition);
+        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
+                .isChecked());
+        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
+                .isChecked());
+        assertTrue(mController.getConditionTagAt(
+                EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+    }
+
+    @Test
+    public void testCountdownChecked() {
+        doReturn(false).when(mController).isAlarm(mCountdownCondition);
+        doReturn(true).when(mController).isCountdown(mCountdownCondition);
+
+        mController.bindConditions(mCountdownCondition);
+        assertFalse(mController.getConditionTagAt(EnableZenModeDialog.FOREVER_CONDITION_INDEX).rb
+                .isChecked());
+        assertTrue(mController.getConditionTagAt(EnableZenModeDialog.COUNTDOWN_CONDITION_INDEX).rb
+                .isChecked());
+        assertFalse(mController.getConditionTagAt(
+                EnableZenModeDialog.COUNTDOWN_ALARM_CONDITION_INDEX).rb.isChecked());
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index f9aa821..2d30f4c 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -47,7 +47,6 @@
     <item type="id" name="qs_icon_tag"/>
     <item type="id" name="qs_slash_tag"/>
     <item type="id" name="scrim"/>
-    <item type="id" name="scrim_blanking"/>
     <item type="id" name="scrim_target"/>
     <item type="id" name="scrim_alpha_start"/>
     <item type="id" name="scrim_alpha_end"/>
@@ -58,6 +57,8 @@
     <item type="id" name="notification_plugin"/>
     <item type="id" name="transformation_start_x_tag"/>
     <item type="id" name="transformation_start_y_tag"/>
+    <item type="id" name="transformation_start_actual_width"/>
+    <item type="id" name="transformation_start_actual_height"/>
     <item type="id" name="transformation_start_scale_x_tag"/>
     <item type="id" name="transformation_start_scale_y_tag"/>
     <item type="id" name="continuous_clipping_tag"/>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
index 9cda75c..a44fd9a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
@@ -51,6 +51,23 @@
         addView(linearLayout);
     }
 
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        if (canScrollVertically(1) || canScrollVertically(-1)) {
+            return super.onInterceptTouchEvent(ev);
+        }
+        return false;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        if (canScrollVertically(1) || canScrollVertically(-1)) {
+            return super.onTouchEvent(ev);
+        }
+        return false;
+    }
+
     public boolean shouldIntercept(MotionEvent ev) {
         if (ev.getY() > (getBottom() - mFooterHeight)) {
             // Do not intercept touches that are below the divider between QS and the footer.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
index 8227b77..d3a325d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ImageTransformState.java
@@ -21,6 +21,8 @@
 import android.view.View;
 import android.widget.ImageView;
 
+import com.android.internal.widget.MessagingImageMessage;
+import com.android.internal.widget.MessagingMessage;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
@@ -117,13 +119,15 @@
 
     @Override
     protected boolean transformScale(TransformState otherState) {
-        return true;
+        return sameAs(otherState);
     }
 
     @Override
     public void recycle() {
         super.recycle();
-        sInstancePool.release(this);
+        if (getClass() == ImageTransformState.class) {
+            sInstancePool.release(this);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java
new file mode 100644
index 0000000..b97995d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingImageTransformState.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.statusbar.notification;
+
+import android.util.Pools;
+import android.view.View;
+
+import com.android.internal.widget.MessagingImageMessage;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.statusbar.ViewTransformationHelper;
+
+/**
+ * A transform state of a image view.
+*/
+public class MessagingImageTransformState extends ImageTransformState {
+    private static Pools.SimplePool<MessagingImageTransformState> sInstancePool
+            = new Pools.SimplePool<>(40);
+    private static final int START_ACTUAL_WIDTH = R.id.transformation_start_actual_width;
+    private static final int START_ACTUAL_HEIGHT = R.id.transformation_start_actual_height;
+    private MessagingImageMessage mImageMessage;
+
+    @Override
+    public void initFrom(View view, TransformInfo transformInfo) {
+        super.initFrom(view, transformInfo);
+        mImageMessage = (MessagingImageMessage) view;
+    }
+
+    @Override
+    protected boolean sameAs(TransformState otherState) {
+        if (super.sameAs(otherState)) {
+            return true;
+        }
+        if (otherState instanceof MessagingImageTransformState) {
+            MessagingImageTransformState otherMessage = (MessagingImageTransformState) otherState;
+            return mImageMessage.sameAs(otherMessage.mImageMessage);
+        }
+        return false;
+    }
+
+    public static MessagingImageTransformState obtain() {
+        MessagingImageTransformState instance = sInstancePool.acquire();
+        if (instance != null) {
+            return instance;
+        }
+        return new MessagingImageTransformState();
+    }
+
+    @Override
+    protected boolean transformScale(TransformState otherState) {
+        return false;
+    }
+
+    @Override
+    protected void transformViewFrom(TransformState otherState, int transformationFlags,
+            ViewTransformationHelper.CustomTransformation customTransformation,
+            float transformationAmount) {
+        super.transformViewFrom(otherState, transformationFlags, customTransformation,
+                transformationAmount);
+        float interpolatedValue = mDefaultInterpolator.getInterpolation(
+                transformationAmount);
+        if (otherState instanceof MessagingImageTransformState && sameAs(otherState)) {
+            MessagingImageMessage otherMessage
+                    = ((MessagingImageTransformState) otherState).mImageMessage;
+            if (transformationAmount == 0.0f) {
+                setStartActualWidth(otherMessage.getActualWidth());
+                setStartActualHeight(otherMessage.getActualHeight());
+            }
+            float startActualWidth = getStartActualWidth();
+            mImageMessage.setActualWidth(
+                    (int) NotificationUtils.interpolate(startActualWidth,
+                            mImageMessage.getStaticWidth(),
+                            interpolatedValue));
+            float startActualHeight = getStartActualHeight();
+            mImageMessage.setActualHeight(
+                    (int) NotificationUtils.interpolate(startActualHeight,
+                            mImageMessage.getHeight(),
+                            interpolatedValue));
+        }
+    }
+
+    public int getStartActualWidth() {
+        Object tag = mTransformedView.getTag(START_ACTUAL_WIDTH);
+        return tag == null ? -1 : (int) tag;
+    }
+
+    public void setStartActualWidth(int actualWidth) {
+        mTransformedView.setTag(START_ACTUAL_WIDTH, actualWidth);
+    }
+
+    public int getStartActualHeight() {
+        Object tag = mTransformedView.getTag(START_ACTUAL_HEIGHT);
+        return tag == null ? -1 : (int) tag;
+    }
+
+    public void setStartActualHeight(int actualWidth) {
+        mTransformedView.setTag(START_ACTUAL_HEIGHT, actualWidth);
+    }
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        if (getClass() == MessagingImageTransformState.class) {
+            sInstancePool.release(this);
+        }
+    }
+
+    @Override
+    protected void resetTransformedView() {
+        super.resetTransformedView();
+        mImageMessage.setActualWidth(mImageMessage.getStaticWidth());
+        mImageMessage.setActualHeight(mImageMessage.getHeight());
+    }
+
+    @Override
+    protected void reset() {
+        super.reset();
+        mImageMessage = null;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index 113118a..314a31d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -22,6 +22,7 @@
 import android.view.ViewGroup;
 
 import com.android.internal.widget.MessagingGroup;
+import com.android.internal.widget.MessagingImageMessage;
 import com.android.internal.widget.MessagingLayout;
 import com.android.internal.widget.MessagingLinearLayout;
 import com.android.internal.widget.MessagingMessage;
@@ -30,6 +31,7 @@
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /**
  * A transform state of the action list
@@ -156,6 +158,7 @@
         }
         appear(ownGroup.getAvatar(), transformationAmount);
         appear(ownGroup.getSenderView(), transformationAmount);
+        appear(ownGroup.getIsolatedMessage(), transformationAmount);
         setClippingDeactivated(ownGroup.getSenderView(), true);
         setClippingDeactivated(ownGroup.getAvatar(), true);
     }
@@ -187,12 +190,13 @@
         }
         disappear(ownGroup.getAvatar(), transformationAmount);
         disappear(ownGroup.getSenderView(), transformationAmount);
+        disappear(ownGroup.getIsolatedMessage(), transformationAmount);
         setClippingDeactivated(ownGroup.getSenderView(), true);
         setClippingDeactivated(ownGroup.getAvatar(), true);
     }
 
     private void appear(View child, float transformationAmount) {
-        if (child.getVisibility() == View.GONE) {
+        if (child == null || child.getVisibility() == View.GONE) {
             return;
         }
         TransformState ownState = TransformState.createFrom(child, mTransformInfo);
@@ -201,7 +205,7 @@
     }
 
     private void disappear(View child, float transformationAmount) {
-        if (child.getVisibility() == View.GONE) {
+        if (child == null || child.getVisibility() == View.GONE) {
             return;
         }
         TransformState ownState = TransformState.createFrom(child, mTransformInfo);
@@ -224,22 +228,24 @@
 
     private void transformGroups(MessagingGroup ownGroup, MessagingGroup otherGroup,
             float transformationAmount, boolean to) {
+        boolean useLinearTransformation =
+                otherGroup.getIsolatedMessage() == null && !mTransformInfo.isAnimating();
         transformView(transformationAmount, to, ownGroup.getSenderView(), otherGroup.getSenderView(),
-                true /* sameAsAny */);
+                true /* sameAsAny */, useLinearTransformation);
         transformView(transformationAmount, to, ownGroup.getAvatar(), otherGroup.getAvatar(),
-                true /* sameAsAny */);
-        MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
-        MessagingLinearLayout otherMessages = otherGroup.getMessageContainer();
+                true /* sameAsAny */, useLinearTransformation);
+        List<MessagingMessage> ownMessages = ownGroup.getMessages();
+        List<MessagingMessage> otherMessages = otherGroup.getMessages();
         float previousTranslation = 0;
-        for (int i = 0; i < ownMessages.getChildCount(); i++) {
-            View child = ownMessages.getChildAt(ownMessages.getChildCount() - 1 - i);
+        for (int i = 0; i < ownMessages.size(); i++) {
+            View child = ownMessages.get(ownMessages.size() - 1 - i).getView();
             if (isGone(child)) {
                 continue;
             }
-            int otherIndex = otherMessages.getChildCount() - 1 - i;
+            int otherIndex = otherMessages.size() - 1 - i;
             View otherChild = null;
             if (otherIndex >= 0) {
-                otherChild = otherMessages.getChildAt(otherIndex);
+                otherChild = otherMessages.get(otherIndex).getView();
                 if (isGone(otherChild)) {
                     otherChild = null;
                 }
@@ -252,7 +258,12 @@
                     transformationAmount = 1.0f - transformationAmount;
                 }
             }
-            transformView(transformationAmount, to, child, otherChild, false /* sameAsAny */);
+            transformView(transformationAmount, to, child, otherChild, false, /* sameAsAny */
+                    useLinearTransformation);
+            if (transformationAmount == 0.0f
+                    && otherGroup.getIsolatedMessage() == otherChild) {
+                ownGroup.setTransformingImages(true);
+            }
             if (otherChild == null) {
                 child.setTranslationY(previousTranslation);
                 setClippingDeactivated(child, true);
@@ -264,12 +275,13 @@
                 previousTranslation = child.getTranslationY();
             }
         }
+        ownGroup.updateClipRect();
     }
 
     private void transformView(float transformationAmount, boolean to, View ownView,
-            View otherView, boolean sameAsAny) {
+            View otherView, boolean sameAsAny, boolean useLinearTransformation) {
         TransformState ownState = TransformState.createFrom(ownView, mTransformInfo);
-        if (!mTransformInfo.isAnimating()) {
+        if (useLinearTransformation) {
             ownState.setDefaultInterpolator(Interpolators.LINEAR);
         }
         ownState.setIsSameAsAnyView(sameAsAny);
@@ -339,11 +351,15 @@
             if (!isGone(ownGroup)) {
                 MessagingLinearLayout ownMessages = ownGroup.getMessageContainer();
                 for (int j = 0; j < ownMessages.getChildCount(); j++) {
-                    MessagingMessage child = (MessagingMessage) ownMessages.getChildAt(j);
+                    View child = ownMessages.getChildAt(j);
                     setVisible(child, visible, force);
                 }
                 setVisible(ownGroup.getAvatar(), visible, force);
                 setVisible(ownGroup.getSenderView(), visible, force);
+                MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage();
+                if (isolatedMessage != null) {
+                    setVisible(isolatedMessage, visible, force);
+                }
             }
         }
     }
@@ -375,11 +391,17 @@
                 }
                 resetTransformedView(ownGroup.getAvatar());
                 resetTransformedView(ownGroup.getSenderView());
+                MessagingImageMessage isolatedMessage = ownGroup.getIsolatedMessage();
+                if (isolatedMessage != null) {
+                    resetTransformedView(isolatedMessage);
+                }
                 setClippingDeactivated(ownGroup.getAvatar(), false);
                 setClippingDeactivated(ownGroup.getSenderView(), false);
                 ownGroup.setTranslationY(0);
                 ownGroup.getMessageContainer().setTranslationY(0);
             }
+            ownGroup.setTransformingImages(false);
+            ownGroup.updateClipRect();
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
index 918b6ed..fc8ceb6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/TransformState.java
@@ -26,6 +26,7 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
+import com.android.internal.widget.MessagingImageMessage;
 import com.android.internal.widget.MessagingPropertyAnimator;
 import com.android.internal.widget.ViewClippingUtil;
 import com.android.systemui.Interpolators;
@@ -80,7 +81,7 @@
     private boolean mSameAsAny;
     private float mTransformationEndY = UNDEFINED;
     private float mTransformationEndX = UNDEFINED;
-    private Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
+    protected Interpolator mDefaultInterpolator = Interpolators.FAST_OUT_SLOW_IN;
 
     public void initFrom(View view, TransformInfo transformInfo) {
         mTransformedView = view;
@@ -131,7 +132,7 @@
         transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount);
     }
 
-    private void transformViewFrom(TransformState otherState, int transformationFlags,
+    protected void transformViewFrom(TransformState otherState, int transformationFlags,
             ViewTransformationHelper.CustomTransformation customTransformation,
             float transformationAmount) {
         final View transformedView = mTransformedView;
@@ -449,6 +450,11 @@
             result.initFrom(view, transformInfo);
             return result;
         }
+        if (view instanceof MessagingImageMessage) {
+            MessagingImageTransformState result = MessagingImageTransformState.obtain();
+            result.initFrom(view, transformInfo);
+            return result;
+        }
         if (view instanceof ImageView) {
             ImageTransformState result = ImageTransformState.obtain();
             result.initFrom(view, transformInfo);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
index 0d36efd..7db6e28 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarGestureHelper.java
@@ -157,6 +157,20 @@
         return (mDockWindowEnabled && interceptDockWindowEvent(event));
     }
 
+    public boolean onTouchEvent(MotionEvent event) {
+        // The same down event was just sent on intercept and therefore can be ignored here
+        boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN
+                && mOverviewEventSender.getProxy() != null;
+        boolean result = mStatusBar.isPresenterFullyCollapsed()
+                && (mQuickScrubController.onTouchEvent(event)
+                || ignoreProxyDownEvent
+                || proxyMotionEvents(event));
+        if (mDockWindowEnabled) {
+            result |= handleDockWindowEvent(event);
+        }
+        return result;
+    }
+
     public void onDraw(Canvas canvas) {
         if (mOverviewEventSender.getProxy() != null) {
             mQuickScrubController.onDraw(canvas);
@@ -307,20 +321,6 @@
         return DRAG_MODE_RECENTS;
     }
 
-    public boolean onTouchEvent(MotionEvent event) {
-        // The same down event was just sent on intercept and therefore can be ignored here
-        boolean ignoreProxyDownEvent = event.getAction() == MotionEvent.ACTION_DOWN
-                && mOverviewEventSender.getProxy() != null;
-        boolean result = mStatusBar.isPresenterFullyCollapsed()
-                && (mQuickScrubController.onTouchEvent(event)
-                || ignoreProxyDownEvent
-                || proxyMotionEvents(event));
-        if (mDockWindowEnabled) {
-            result |= handleDockWindowEvent(event);
-        }
-        return result;
-    }
-
     @Override
     public void onTuningChanged(String key, String newValue) {
         switch (key) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index c37dd55..c504ed8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -277,14 +277,6 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        if (mGestureHelper.onTouchEvent(event)) {
-            return true;
-        }
-        return mRecentsAnimationStarted || super.onTouchEvent(event);
-    }
-
-    @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
         int action = event.getActionMasked();
         if (action == MotionEvent.ACTION_DOWN) {
@@ -300,9 +292,18 @@
                 }
             }
         }
+
         return mGestureHelper.onInterceptTouchEvent(event) || mRecentsAnimationStarted;
     }
 
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mGestureHelper.onTouchEvent(event)) {
+            return true;
+        }
+        return mRecentsAnimationStarted || super.onTouchEvent(event);
+    }
+
     public void abortCurrentGesture() {
         getHomeButton().abortCurrentGesture();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
index 001a1a2..6bfaaf4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickScrubController.java
@@ -158,8 +158,9 @@
                     } catch (RemoteException e) {
                         Log.e(TAG, "Failed to send start of quick switch.", e);
                     }
+                    return true;
                 }
-                return true;
+                return false;
             }
         };
 
@@ -189,6 +190,10 @@
         mNavigationBarView = navigationBarView;
     }
 
+    /**
+     * @return true if we want to intercept touch events for quick scrub/switch and prevent proxying
+     *         the event to the overview service.
+     */
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
         final IOverviewProxy overviewProxy = mOverviewEventSender.getProxy();
@@ -197,7 +202,10 @@
             homeButton.setDelayTouchFeedback(false);
             return false;
         }
-        mGestureDetector.onTouchEvent(event);
+        if (mGestureDetector.onTouchEvent(event)) {
+            // If the fling has been handled, then skip proxying the UP
+            return true;
+        }
         int action = event.getAction();
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
@@ -240,8 +248,9 @@
                         offset = pos - mTrackRect.left;
                         trackSize = mTrackRect.width();
                     }
-                    // Do not start scrubbing when dragging in the perpendicular direction
-                    if (!mDraggingActive && exceededPerpendicularTouchSlop) {
+                    // Do not start scrubbing when dragging in the perpendicular direction if we
+                    // haven't already started quickscrub
+                    if (!mDraggingActive && !mQuickScrubActive && exceededPerpendicularTouchSlop) {
                         mHandler.removeCallbacksAndMessages(null);
                         return false;
                     }
@@ -295,6 +304,22 @@
         return mDraggingActive || mQuickScrubActive;
     }
 
+    /**
+     * @return true if we want to handle touch events for quick scrub/switch and prevent proxying
+     *         the event to the overview service.
+     */
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mGestureDetector.onTouchEvent(event)) {
+            // If the fling has been handled, then skip proxying the UP
+            return true;
+        }
+        if (event.getAction() == MotionEvent.ACTION_UP) {
+            endQuickScrub();
+        }
+        return mDraggingActive || mQuickScrubActive;
+    }
+
     @Override
     public void onDraw(Canvas canvas) {
         int color = (int) mTrackColorEvaluator.evaluate(mDarkIntensity, mLightTrackColor,
@@ -341,14 +366,6 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        if (event.getAction() == MotionEvent.ACTION_UP) {
-            endQuickScrub();
-        }
-        return false;
-    }
-
-    @Override
     public void setBarState(boolean isVertical, boolean isRTL) {
         mIsVertical = isVertical;
         mIsRTL = isRTL;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 44e0c25..2b16e74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -30,6 +30,7 @@
 import android.os.Trace;
 import android.util.Log;
 import android.util.MathUtils;
+import android.view.Choreographer;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
@@ -111,7 +112,6 @@
     protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
 
     static final int TAG_KEY_ANIM = R.id.scrim;
-    static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking;
     private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
     private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
     private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
@@ -166,6 +166,7 @@
     private boolean mScreenBlankingCallbackCalled;
     private Callback mCallback;
     private boolean mWallpaperSupportsAmbientMode;
+    private Choreographer.FrameCallback mPendingFrameCallback;
 
     private final WakeLock mWakeLock;
     private boolean mWakeLockHeld;
@@ -248,6 +249,11 @@
         mCurrentInFrontAlpha = state.getFrontAlpha();
         mCurrentBehindAlpha = state.getBehindAlpha();
 
+        if (mPendingFrameCallback != null) {
+            Choreographer.getInstance().removeFrameCallback(mPendingFrameCallback);
+            mPendingFrameCallback = null;
+        }
+
         // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
         // to do the same when you're just showing the brightness mirror.
         mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
@@ -276,13 +282,18 @@
             mWallpaperVisibilityTimedOut = false;
         }
 
-        if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
-            scheduleUpdate();
-        } else {
+        if (mKeyguardUpdateMonitor.needsSlowUnlockTransition() && mState == ScrimState.UNLOCKED) {
             // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
             // with too many things at this case, in order to not skip the initial frames.
             mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
             mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
+        } else if (!mDozeParameters.getAlwaysOn() && oldState == ScrimState.AOD) {
+            // Execute first frame immediately when display was completely off.
+            // Scheduling a frame isn't enough because the system may aggressively enter doze,
+            // delaying callbacks or never triggering them until the power button is pressed.
+            onPreDraw();
+        } else {
+            scheduleUpdate();
         }
     }
 
@@ -687,11 +698,12 @@
             }
         }
 
-        final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null;
-        if (mBlankScreen || blankingInProgress) {
-            if (!blankingInProgress) {
-                blankDisplay();
-            }
+        if (mPendingFrameCallback != null) {
+            // Display is off and we're waiting.
+            return;
+        } else if (mBlankScreen) {
+            // Need to blank the display before continuing.
+            blankDisplay();
             return;
         } else if (!mScreenBlankingCallbackCalled) {
             // Not blanking the screen. Letting the callback know that we're ready
@@ -745,45 +757,33 @@
     }
 
     private void blankDisplay() {
-        final float initialAlpha = mScrimInFront.getViewAlpha();
-        final int initialTint = mScrimInFront.getTint();
-        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
-        anim.addUpdateListener(animation -> {
-            final float amount = (float) animation.getAnimatedValue();
-            float animAlpha = MathUtils.lerp(initialAlpha, 1, amount);
-            int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount);
-            updateScrimColor(mScrimInFront, animAlpha, animTint);
-            dispatchScrimsVisible();
-        });
-        anim.setInterpolator(getInterpolator());
-        anim.setDuration(mDozeParameters.getPulseInDuration());
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                if (mCallback != null) {
-                    mCallback.onDisplayBlanked();
-                    mScreenBlankingCallbackCalled = true;
-                }
-                Runnable blankingCallback = () -> {
-                    mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null);
-                    mBlankScreen = false;
-                    // Try again.
-                    updateScrims();
-                };
+        updateScrimColor(mScrimInFront, 1, Color.BLACK);
 
-                // Setting power states can happen after we push out the frame. Make sure we
-                // stay fully opaque until the power state request reaches the lower levels.
-                getHandler().postDelayed(blankingCallback, 100);
-
+        // Notify callback that the screen is completely black and we're
+        // ready to change the display power mode
+        mPendingFrameCallback = frameTimeNanos -> {
+            if (mCallback != null) {
+                mCallback.onDisplayBlanked();
+                mScreenBlankingCallbackCalled = true;
             }
-        });
-        anim.start();
-        mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim);
 
-        // Finish animation if we're already at its final state
-        if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) {
-            anim.end();
-        }
+            Runnable blankingCallback = () -> {
+                mPendingFrameCallback = null;
+                mBlankScreen = false;
+                // Try again.
+                updateScrims();
+            };
+
+            // Setting power states can happen after we push out the frame. Make sure we
+            // stay fully opaque until the power state request reaches the lower levels.
+            getHandler().postDelayed(blankingCallback, 100);
+        };
+        doOnTheNextFrame(mPendingFrameCallback);
+    }
+
+    @VisibleForTesting
+    protected void doOnTheNextFrame(Choreographer.FrameCallback callback) {
+        Choreographer.getInstance().postFrameCallback(callback);
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
index 314d6aa..381e4af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java
@@ -48,7 +48,6 @@
                     // set our scrim to black in this frame to avoid flickering and
                     // fade it out afterwards.
                     mBlankScreen = true;
-                    updateScrimColor(mScrimInFront, 1, Color.BLACK);
                 }
             } else {
                 mAnimationDuration = ScrimController.ANIMATION_DURATION;
@@ -86,9 +85,6 @@
     AOD(3) {
         @Override
         public void prepare(ScrimState previousState) {
-            if (previousState == ScrimState.PULSING && !mCanControlScreenOff) {
-                updateScrimColor(mScrimInFront, 1, Color.BLACK);
-            }
             final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
             final boolean wasPulsing = previousState == ScrimState.PULSING;
             mBlankScreen = wasPulsing && !mCanControlScreenOff;
@@ -115,9 +111,6 @@
                     && !mKeyguardUpdateMonitor.hasLockscreenWallpaper() ? 0f : 1f;
             mCurrentBehindTint = Color.BLACK;
             mBlankScreen = mDisplayRequiresBlanking;
-            if (mDisplayRequiresBlanking) {
-                updateScrimColor(mScrimInFront, 1, Color.BLACK);
-            }
         }
     },
 
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index 46c43c2..6ed07f8 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -49,6 +49,8 @@
 import android.view.Window;
 import android.view.WindowManager;
 
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
 import com.android.systemui.Dependency;
@@ -202,6 +204,7 @@
     @Override
     public void show() {
         super.show();
+        Dependency.get(MetricsLogger.class).visible(MetricsProto.MetricsEvent.OUTPUT_CHOOSER);
         mHardwareLayout.setTranslationX(getAnimTranslation());
         mHardwareLayout.setAlpha(0);
         mHardwareLayout.animate()
@@ -215,6 +218,7 @@
 
     @Override
     public void dismiss() {
+        Dependency.get(MetricsLogger.class).hidden(MetricsProto.MetricsEvent.OUTPUT_CHOOSER);
         mHardwareLayout.setTranslationX(0);
         mHardwareLayout.setAlpha(1);
         mHardwareLayout.animate()
@@ -237,11 +241,15 @@
         if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
             final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
             if (device.getMaxConnectionState() == BluetoothProfile.STATE_DISCONNECTED) {
+                Dependency.get(MetricsLogger.class).action(
+                        MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT);
                 mBluetoothController.connect(device);
             }
         } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
             final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag;
             if (route.isEnabled()) {
+                Dependency.get(MetricsLogger.class).action(
+                        MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_CONNECT);
                 route.select();
             }
         }
@@ -252,8 +260,12 @@
         if (item == null || item.tag == null) return;
         if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
             final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+            Dependency.get(MetricsLogger.class).action(
+                    MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT);
             mBluetoothController.disconnect(device);
         } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+            Dependency.get(MetricsLogger.class).action(
+                    MetricsProto.MetricsEvent.ACTION_OUTPUT_CHOOSER_DISCONNECT);
             mRouter.unselect(UNSELECT_REASON_DISCONNECTED);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index a131a61..c622677 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -396,13 +396,9 @@
                 if (hasVibrator) {
                     mController.setRingerMode(AudioManager.RINGER_MODE_VIBRATE, false);
                 } else {
-                    final boolean wasZero = ss.level == 0;
-                    mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0);
                     mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
                 }
             } else if (mState.ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE) {
-                final boolean wasZero = ss.level == 0;
-                mController.setStreamVolume(AudioManager.STREAM_RING, wasZero ? 1 : 0);
                 mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
             } else {
                 mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 6d2691c..43e16db 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -38,6 +38,7 @@
 import android.support.test.filters.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.Choreographer;
 import android.view.View;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -374,7 +375,6 @@
             onPreDraw();
 
             // Force finish screen blanking.
-            endAnimation(mScrimInFront, TAG_KEY_ANIM_BLANK);
             mHandler.dispatchQueuedMessages();
             // Force finish all animations.
             endAnimation(mScrimBehind, TAG_KEY_ANIM);
@@ -401,6 +401,15 @@
         protected WakeLock createWakeLock() {
             return mWakeLock;
         }
+
+        /**
+         * Do not wait for a frame since we're in a test environment.
+         * @param callback What to execute.
+         */
+        @Override
+        protected void doOnTheNextFrame(Choreographer.FrameCallback callback) {
+            callback.doFrame(0);
+        }
     }
 
 }
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index 01714cf..ae5e133 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -5171,6 +5171,7 @@
     // An autofill service was bound using an unofficial(but still supported) permission.
     // Package: Package of the autofill service
     // OS: P
+
     AUTOFILL_INVALID_PERMISSION = 1289;
 
     // OPEN: QS Alarm tile shown
@@ -5185,6 +5186,33 @@
     // OS: P
     USB_DEVICE_DETAILS = 1291;
 
+    // OPEN: Settings > Accessibility > Vibration
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACCESSIBILITY_VIBRATION = 1292;
+
+    // OPEN: Settings > Accessibility > Vibration > Ring & notification vibration
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACCESSIBILITY_VIBRATION_NOTIFICATION = 1293;
+
+    // OPEN: Settings > Accessibility > Vibration > Touch vibration
+    // CATEGORY: SETTINGS
+    // OS: P
+    ACCESSIBILITY_VIBRATION_TOUCH = 1294;
+
+    // OPEN: Volume panel > output chooser dialog
+    // OS: P
+    OUTPUT_CHOOSER = 1295;
+
+    // Action: Volume panel > output chooser dialog > tap on device
+    // OS: P
+    ACTION_OUTPUT_CHOOSER_CONNECT = 1296;
+
+    // Action: Volume panel > output chooser dialog > tap on X next to connected device
+    // OS: P
+    ACTION_OUTPUT_CHOOSER_DISCONNECT = 1297;
+
     // ---- End P Constants, all P constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index b32be73..52d0e08e 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -24,8 +24,9 @@
 #include <utils/misc.h>
 #include <inttypes.h>
 
+#include <android-base/macros.h>
 #include <androidfw/Asset.h>
-#include <androidfw/AssetManager.h>
+#include <androidfw/AssetManager2.h>
 #include <androidfw/ResourceTypes.h>
 #include <android-base/macros.h>
 
@@ -1664,18 +1665,22 @@
 static jlong
 nFileA3DCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path)
 {
-    AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr);
+    Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr);
     if (mgr == nullptr) {
         return 0;
     }
 
     AutoJavaStringToUTF8 str(_env, _path);
-    Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
-    if (asset == nullptr) {
-        return 0;
+    std::unique_ptr<Asset> asset;
+    {
+        ScopedLock<AssetManager2> locked_mgr(*mgr);
+        asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER);
+        if (asset == nullptr) {
+            return 0;
+        }
     }
 
-    jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset);
+    jlong id = (jlong)(uintptr_t)rsaFileA3DCreateFromAsset((RsContext)con, asset.release());
     return id;
 }
 
@@ -1752,22 +1757,25 @@
 nFontCreateFromAsset(JNIEnv *_env, jobject _this, jlong con, jobject _assetMgr, jstring _path,
                      jfloat fontSize, jint dpi)
 {
-    AssetManager* mgr = assetManagerForJavaObject(_env, _assetMgr);
+    Guarded<AssetManager2>* mgr = AssetManagerForJavaObject(_env, _assetMgr);
     if (mgr == nullptr) {
         return 0;
     }
 
     AutoJavaStringToUTF8 str(_env, _path);
-    Asset* asset = mgr->open(str.c_str(), Asset::ACCESS_BUFFER);
-    if (asset == nullptr) {
-        return 0;
+    std::unique_ptr<Asset> asset;
+    {
+        ScopedLock<AssetManager2> locked_mgr(*mgr);
+        asset = locked_mgr->Open(str.c_str(), Asset::ACCESS_BUFFER);
+        if (asset == nullptr) {
+            return 0;
+        }
     }
 
     jlong id = (jlong)(uintptr_t)rsFontCreateFromMemory((RsContext)con,
                                            str.c_str(), str.length(),
                                            fontSize, dpi,
                                            asset->getBuffer(false), asset->getLength());
-    delete asset;
     return id;
 }
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 6108afa..18f49ec 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -947,7 +947,7 @@
 
         final int flags = lastResponse.getFlags();
         if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
-            if (sDebug) Slog.d(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
+            if (sVerbose) Slog.v(TAG, "logContextCommittedLocked(): ignored by flags " + flags);
             return;
         }
 
@@ -1431,7 +1431,10 @@
                     }
                 }
 
-                if (sDebug) Slog.d(TAG, "Good news, everyone! All checks passed, show save UI!");
+                if (sDebug) {
+                    Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for "
+                            + id + "!");
+                }
 
                 // Use handler so logContextCommitted() is logged first
                 mHandlerCaller.getHandler().post(() -> mService.logSaveShown(id, mClientState));
@@ -1455,7 +1458,7 @@
         }
         // Nothing changed...
         if (sDebug) {
-            Slog.d(TAG, "showSaveLocked(): with no changes, comes no responsibilities."
+            Slog.d(TAG, "showSaveLocked(" + id +"): with no changes, comes no responsibilities."
                     + "allRequiredAreNotNull=" + allRequiredAreNotEmpty
                     + ", atLeastOneChanged=" + atLeastOneChanged);
         }
@@ -1970,7 +1973,7 @@
         try {
             if (sVerbose) {
                 Slog.v(TAG, "updateTrackedIdsLocked(): " + trackedViews + " => " + fillableIds
-                        + " (triggering on " + saveTriggerId + ")");
+                        + " triggerId: " + saveTriggerId + " saveOnFinish:" + saveOnFinish);
             }
             mClient.setTrackedViews(id, toArray(trackedViews), saveOnAllViewsInvisible,
                     saveOnFinish, toArray(fillableIds), saveTriggerId);
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index b4af432..355da2d 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -98,7 +98,8 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.LocalLog;
-import com.android.server.ForceAppStandbyTracker.Listener;
+import com.android.internal.util.Preconditions;
+import com.android.server.AppStateTracker.Listener;
 
 /**
  * Alarm manager implementaion.
@@ -249,7 +250,7 @@
     private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
             new SparseArray<>();
 
-    private final ForceAppStandbyTracker mForceAppStandbyTracker;
+    private AppStateTracker mAppStateTracker;
     private boolean mAppStandbyParole;
     private ArrayMap<Pair<String, Integer>, Long> mLastAlarmDeliveredForPackage = new ArrayMap<>();
 
@@ -707,9 +708,6 @@
         super(context);
         mConstants = new Constants(mHandler);
 
-        mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
-        mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
-
         publishLocalService(AlarmManagerInternal.class, new LocalService());
     }
 
@@ -1329,13 +1327,15 @@
     @Override
     public void onBootPhase(int phase) {
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
-            mForceAppStandbyTracker.start();
             mConstants.start(getContext().getContentResolver());
             mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
             mLocalDeviceIdleController
                     = LocalServices.getService(DeviceIdleController.LocalService.class);
             mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
             mUsageStatsManagerInternal.addAppIdleStateChangeListener(new AppStandbyTracker());
+
+            mAppStateTracker = LocalServices.getService(AppStateTracker.class);
+            mAppStateTracker.addListener(mForceAppStandbyListener);
         }
     }
 
@@ -1729,7 +1729,8 @@
             // timing restrictions.
             } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
                     || callingUid == mSystemUiUid
-                    || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
+                    || (mAppStateTracker != null
+                        && mAppStateTracker.isUidPowerSaveWhitelisted(callingUid)))) {
                 flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
                 flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
             }
@@ -1812,8 +1813,10 @@
             mConstants.dump(pw);
             pw.println();
 
-            mForceAppStandbyTracker.dump(pw, "  ");
-            pw.println();
+            if (mAppStateTracker != null) {
+                mAppStateTracker.dump(pw, "  ");
+                pw.println();
+            }
 
             pw.println("  App Standby Parole: " + mAppStandbyParole);
             pw.println();
@@ -2161,8 +2164,10 @@
 
             mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
 
-            mForceAppStandbyTracker.dumpProto(proto,
-                    AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
+            if (mAppStateTracker != null) {
+                mAppStateTracker.dumpProto(proto,
+                        AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
+            }
 
             proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
             if (!mInteractive) {
@@ -2942,7 +2947,7 @@
         }
         final String sourcePackage = alarm.sourcePackage;
         final int sourceUid = alarm.creatorUid;
-        return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage,
+        return mAppStateTracker.areAlarmsRestricted(sourceUid, sourcePackage,
                 allowWhileIdle);
     }
 
@@ -2955,7 +2960,7 @@
 
     private long getWhileIdleMinIntervalLocked(int uid) {
         final boolean dozing = mPendingIdleUntil != null;
-        final boolean ebs = mForceAppStandbyTracker.isForceAllAppsStandbyEnabled();
+        final boolean ebs = mAppStateTracker.isForceAllAppsStandbyEnabled();
         if (!dozing && !ebs) {
             return mConstants.ALLOW_WHILE_IDLE_SHORT_TIME;
         }
@@ -3528,10 +3533,15 @@
         public static final int REPORT_ALARMS_ACTIVE = 4;
         public static final int APP_STANDBY_BUCKET_CHANGED = 5;
         public static final int APP_STANDBY_PAROLE_CHANGED = 6;
+        public static final int REMOVE_FOR_STOPPED = 7;
 
         public AlarmHandler() {
         }
 
+        public void postRemoveForStopped(int uid) {
+            obtainMessage(REMOVE_FOR_STOPPED, uid, 0).sendToTarget();
+        }
+
         public void handleMessage(Message msg) {
             switch (msg.what) {
                 case ALARM_EVENT: {
@@ -3594,6 +3604,12 @@
                     }
                     break;
 
+                case REMOVE_FOR_STOPPED:
+                    synchronized (mLock) {
+                        removeForStoppedLocked(msg.arg1);
+                    }
+                    break;
+
                 default:
                     // nope, just ignore it
                     break;
@@ -3783,10 +3799,8 @@
         }
 
         @Override public void onUidGone(int uid, boolean disabled) {
-            synchronized (mLock) {
-                if (disabled) {
-                    removeForStoppedLocked(uid);
-                }
+            if (disabled) {
+                mHandler.postRemoveForStopped(uid);
             }
         }
 
@@ -3794,10 +3808,8 @@
         }
 
         @Override public void onUidIdle(int uid, boolean disabled) {
-            synchronized (mLock) {
-                if (disabled) {
-                    removeForStoppedLocked(uid);
-                }
+            if (disabled) {
+                mHandler.postRemoveForStopped(uid);
             }
         }
 
@@ -4126,7 +4138,7 @@
             if (allowWhileIdle) {
                 // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
                 mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED);
-                if (mForceAppStandbyTracker.isUidInForeground(alarm.creatorUid)) {
+                if (mAppStateTracker.isUidInForeground(alarm.creatorUid)) {
                     mUseAllowWhileIdleShortTime.put(alarm.creatorUid, true);
                 } else {
                     mUseAllowWhileIdleShortTime.put(alarm.creatorUid, false);
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/AppStateTracker.java
similarity index 91%
rename from services/core/java/com/android/server/ForceAppStandbyTracker.java
rename to services/core/java/com/android/server/AppStateTracker.java
index 339101f..51dff56 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/AppStateTracker.java
@@ -54,7 +54,6 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
-import com.android.server.DeviceIdleController.LocalService;
 import com.android.server.ForceAppStandbyTrackerProto.ExemptedPackage;
 import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
 
@@ -73,14 +72,14 @@
  * TODO: Make it a LocalService.
  *
  * Test:
-  atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+  atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
  */
-public class ForceAppStandbyTracker {
+public class AppStateTracker {
     private static final String TAG = "ForceAppStandbyTracker";
     private static final boolean DEBUG = true;
 
-    @GuardedBy("ForceAppStandbyTracker.class")
-    private static ForceAppStandbyTracker sInstance;
+    @GuardedBy("AppStateTracker.class")
+    private static AppStateTracker sInstance;
 
     private final Object mLock = new Object();
     private final Context mContext;
@@ -89,6 +88,7 @@
     static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
 
     IActivityManager mIActivityManager;
+    ActivityManagerInternal mActivityManagerInternal;
     AppOpsManager mAppOpsManager;
     IAppOpsService mAppOpsService;
     PowerManagerInternal mPowerManagerInternal;
@@ -172,6 +172,9 @@
         int EXEMPT_CHANGED = 6;
         int FORCE_ALL_CHANGED = 7;
         int FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 8;
+
+        int IS_UID_ACTIVE_CACHED = 9;
+        int IS_UID_ACTIVE_RAW = 10;
     }
 
     private final StatLogger mStatLogger = new StatLogger(new String[] {
@@ -184,6 +187,9 @@
             "EXEMPT_CHANGED",
             "FORCE_ALL_CHANGED",
             "FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED",
+
+            "IS_UID_ACTIVE_CACHED",
+            "IS_UID_ACTIVE_RAW",
     });
 
     @VisibleForTesting
@@ -249,7 +255,7 @@
         /**
          * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package.
          */
-        private void onRunAnyAppOpsChanged(ForceAppStandbyTracker sender,
+        private void onRunAnyAppOpsChanged(AppStateTracker sender,
                 int uid, @NonNull String packageName) {
             updateJobsForUidPackage(uid, packageName);
 
@@ -264,14 +270,14 @@
         /**
          * This is called when the foreground state changed for a UID.
          */
-        private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) {
+        private void onUidForegroundStateChanged(AppStateTracker sender, int uid) {
             onUidForeground(uid, sender.isUidInForeground(uid));
         }
 
         /**
          * This is called when the active/idle state changed for a UID.
          */
-        private void onUidActiveStateChanged(ForceAppStandbyTracker sender, int uid) {
+        private void onUidActiveStateChanged(AppStateTracker sender, int uid) {
             updateJobsForUid(uid);
 
             if (sender.isUidActive(uid)) {
@@ -282,7 +288,7 @@
         /**
          * This is called when an app-id(s) is removed from the power save whitelist.
          */
-        private void onPowerSaveUnwhitelisted(ForceAppStandbyTracker sender) {
+        private void onPowerSaveUnwhitelisted(AppStateTracker sender) {
             updateAllJobs();
             unblockAllUnrestrictedAlarms();
         }
@@ -291,14 +297,14 @@
          * This is called when the power save whitelist changes, excluding the
          * {@link #onPowerSaveUnwhitelisted} case.
          */
-        private void onPowerSaveWhitelistedChanged(ForceAppStandbyTracker sender) {
+        private void onPowerSaveWhitelistedChanged(AppStateTracker sender) {
             updateAllJobs();
         }
 
         /**
          * This is called when the temp whitelist changes.
          */
-        private void onTempPowerSaveWhitelistChanged(ForceAppStandbyTracker sender) {
+        private void onTempPowerSaveWhitelistChanged(AppStateTracker sender) {
 
             // TODO This case happens rather frequently; consider optimizing and update jobs
             // only for affected app-ids.
@@ -311,7 +317,7 @@
         /**
          * This is called when the EXEMPT bucket is updated.
          */
-        private void onExemptChanged(ForceAppStandbyTracker sender) {
+        private void onExemptChanged(AppStateTracker sender) {
             // This doesn't happen very often, so just re-evaluate all jobs / alarms.
             updateAllJobs();
             unblockAllUnrestrictedAlarms();
@@ -320,7 +326,7 @@
         /**
          * This is called when the global "force all apps standby" flag changes.
          */
-        private void onForceAllAppsStandbyChanged(ForceAppStandbyTracker sender) {
+        private void onForceAllAppsStandbyChanged(AppStateTracker sender) {
             updateAllJobs();
 
             if (!sender.isForceAllAppsStandbyEnabled()) {
@@ -377,30 +383,15 @@
         }
     }
 
-    @VisibleForTesting
-    ForceAppStandbyTracker(Context context, Looper looper) {
+    public AppStateTracker(Context context, Looper looper) {
         mContext = context;
         mHandler = new MyHandler(looper);
     }
 
-    private ForceAppStandbyTracker(Context context) {
-        this(context, FgThread.get().getLooper());
-    }
-
-    /**
-     * Get the singleton instance.
-     */
-    public static synchronized ForceAppStandbyTracker getInstance(Context context) {
-        if (sInstance == null) {
-            sInstance = new ForceAppStandbyTracker(context);
-        }
-        return sInstance;
-    }
-
     /**
      * Call it when the system is ready.
      */
-    public void start() {
+    public void onSystemServicesReady() {
         synchronized (mLock) {
             if (mStarted) {
                 return;
@@ -408,6 +399,7 @@
             mStarted = true;
 
             mIActivityManager = Preconditions.checkNotNull(injectIActivityManager());
+            mActivityManagerInternal = Preconditions.checkNotNull(injectActivityManagerInternal());
             mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
             mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
             mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
@@ -475,6 +467,11 @@
     }
 
     @VisibleForTesting
+    ActivityManagerInternal injectActivityManagerInternal() {
+        return LocalServices.getService(ActivityManagerInternal.class);
+    }
+
+    @VisibleForTesting
     PowerManagerInternal injectPowerManagerInternal() {
         return LocalServices.getService(PowerManagerInternal.class);
     }
@@ -614,48 +611,22 @@
     private final class UidObserver extends IUidObserver.Stub {
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq) {
-            synchronized (mLock) {
-                if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                    if (removeUidFromArray(mForegroundUids, uid, false)) {
-                        mHandler.notifyUidForegroundStateChanged(uid);
-                    }
-                } else {
-                    if (addUidToArray(mForegroundUids, uid)) {
-                        mHandler.notifyUidForegroundStateChanged(uid);
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void onUidGone(int uid, boolean disabled) {
-            removeUid(uid, true);
+            mHandler.onUidStateChanged(uid, procState);
         }
 
         @Override
         public void onUidActive(int uid) {
-            synchronized (mLock) {
-                if (addUidToArray(mActiveUids, uid)) {
-                    mHandler.notifyUidActiveStateChanged(uid);
-                }
-            }
+            mHandler.onUidActive(uid);
+        }
+
+        @Override
+        public void onUidGone(int uid, boolean disabled) {
+            mHandler.onUidGone(uid, disabled);
         }
 
         @Override
         public void onUidIdle(int uid, boolean disabled) {
-            // Just to avoid excessive memcpy, don't remove from the array in this case.
-            removeUid(uid, false);
-        }
-
-        private void removeUid(int uid, boolean remove) {
-            synchronized (mLock) {
-                if (removeUidFromArray(mActiveUids, uid, remove)) {
-                    mHandler.notifyUidActiveStateChanged(uid);
-                }
-                if (removeUidFromArray(mForegroundUids, uid, remove)) {
-                    mHandler.notifyUidForegroundStateChanged(uid);
-                }
-            }
+            mHandler.onUidIdle(uid, disabled);
         }
 
         @Override
@@ -740,6 +711,11 @@
         private static final int MSG_FORCE_APP_STANDBY_FEATURE_FLAG_CHANGED = 9;
         private static final int MSG_EXEMPT_CHANGED = 10;
 
+        private static final int MSG_ON_UID_STATE_CHANGED = 11;
+        private static final int MSG_ON_UID_ACTIVE = 12;
+        private static final int MSG_ON_UID_GONE = 13;
+        private static final int MSG_ON_UID_IDLE = 14;
+
         public MyHandler(Looper looper) {
             super(looper);
         }
@@ -790,6 +766,22 @@
             obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
         }
 
+        public void onUidStateChanged(int uid, int procState) {
+            obtainMessage(MSG_ON_UID_STATE_CHANGED, uid, procState).sendToTarget();
+        }
+
+        public void onUidActive(int uid) {
+            obtainMessage(MSG_ON_UID_ACTIVE, uid, 0).sendToTarget();
+        }
+
+        public void onUidGone(int uid, boolean disabled) {
+            obtainMessage(MSG_ON_UID_GONE, uid, disabled ? 1 : 0).sendToTarget();
+        }
+
+        public void onUidIdle(int uid, boolean disabled) {
+            obtainMessage(MSG_ON_UID_IDLE, uid, disabled ? 1 : 0).sendToTarget();
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -804,7 +796,7 @@
                     return;
                 }
             }
-            final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this;
+            final AppStateTracker sender = AppStateTracker.this;
 
             long start = mStatLogger.getTime();
             switch (msg.what) {
@@ -883,6 +875,61 @@
                 case MSG_USER_REMOVED:
                     handleUserRemoved(msg.arg1);
                     return;
+
+                case MSG_ON_UID_STATE_CHANGED:
+                    handleUidStateChanged(msg.arg1, msg.arg2);
+                    return;
+                case MSG_ON_UID_ACTIVE:
+                    handleUidActive(msg.arg1);
+                    return;
+                case MSG_ON_UID_GONE:
+                    handleUidGone(msg.arg1, msg.arg1 != 0);
+                    return;
+                case MSG_ON_UID_IDLE:
+                    handleUidIdle(msg.arg1, msg.arg1 != 0);
+                    return;
+            }
+        }
+
+        public void handleUidStateChanged(int uid, int procState) {
+            synchronized (mLock) {
+                if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                    if (removeUidFromArray(mForegroundUids, uid, false)) {
+                        mHandler.notifyUidForegroundStateChanged(uid);
+                    }
+                } else {
+                    if (addUidToArray(mForegroundUids, uid)) {
+                        mHandler.notifyUidForegroundStateChanged(uid);
+                    }
+                }
+            }
+        }
+
+        public void handleUidActive(int uid) {
+            synchronized (mLock) {
+                if (addUidToArray(mActiveUids, uid)) {
+                    mHandler.notifyUidActiveStateChanged(uid);
+                }
+            }
+        }
+
+        public void handleUidGone(int uid, boolean disabled) {
+            removeUid(uid, true);
+        }
+
+        public void handleUidIdle(int uid, boolean disabled) {
+            // Just to avoid excessive memcpy, don't remove from the array in this case.
+            removeUid(uid, false);
+        }
+
+        private void removeUid(int uid, boolean remove) {
+            synchronized (mLock) {
+                if (removeUidFromArray(mActiveUids, uid, remove)) {
+                    mHandler.notifyUidActiveStateChanged(uid);
+                }
+                if (removeUidFromArray(mForegroundUids, uid, remove)) {
+                    mHandler.notifyUidForegroundStateChanged(uid);
+                }
             }
         }
     }
@@ -1039,11 +1086,11 @@
     }
 
     /**
-     * @return whether a UID is in active or not.
+     * @return whether a UID is in active or not *based on cached information.*
      *
      * Note this information is based on the UID proc state callback, meaning it's updated
      * asynchronously and may subtly be stale. If the fresh data is needed, use
-     * {@link ActivityManagerInternal#getUidProcessState} instead.
+     * {@link #isUidActiveSynced} instead.
      */
     public boolean isUidActive(int uid) {
         if (UserHandle.isCore(uid)) {
@@ -1055,6 +1102,23 @@
     }
 
     /**
+     * @return whether a UID is in active or not *right now.*
+     *
+     * This gives the fresh information, but may access the activity manager so is slower.
+     */
+    public boolean isUidActiveSynced(int uid) {
+        if (isUidActive(uid)) { // Use the cached one first.
+            return true;
+        }
+        final long start = mStatLogger.getTime();
+
+        final boolean ret = mActivityManagerInternal.isUidActive(uid);
+        mStatLogger.logDurationStat(Stats.IS_UID_ACTIVE_RAW, start);
+
+        return ret;
+    }
+
+    /**
      * @return whether a UID is in the foreground or not.
      *
      * Note this information is based on the UID proc state callback, meaning it's updated
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index 44974ff..2b3c585 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -82,6 +82,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.net.NetworkPolicyManagerInternal;
@@ -128,6 +129,7 @@
     private Intent mIdleIntent;
     private Intent mLightIdleIntent;
     private AnyMotionDetector mAnyMotionDetector;
+    private final AppStateTracker mAppStateTracker;
     private boolean mLightEnabled;
     private boolean mDeepEnabled;
     private boolean mForceIdle;
@@ -1371,6 +1373,8 @@
         super(context);
         mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
         mHandler = new MyHandler(BackgroundThread.getHandler().getLooper());
+        mAppStateTracker = new AppStateTracker(context, FgThread.get().getLooper());
+        LocalServices.addService(AppStateTracker.class, mAppStateTracker);
     }
 
     boolean isAppOnWhitelistInternal(int appid) {
@@ -1501,6 +1505,8 @@
                         (PowerManager) getContext().getSystemService(Context.POWER_SERVICE),
                         mHandler, mSensorManager, this, angleThreshold);
 
+                mAppStateTracker.onSystemServicesReady();
+
                 mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
                 mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
@@ -2615,7 +2621,7 @@
     }
 
     private void passWhiteListToForceAppStandbyTrackerLocked() {
-        ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds(
+        mAppStateTracker.setPowerSaveWhitelistAppIds(
                 mPowerSaveWhitelistExceptIdleAppIdArray,
                 mTempWhitelistAppIdArray);
     }
diff --git a/services/core/java/com/android/server/StatLogger.java b/services/core/java/com/android/server/StatLogger.java
index f211731..0e6f5e2 100644
--- a/services/core/java/com/android/server/StatLogger.java
+++ b/services/core/java/com/android/server/StatLogger.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import android.os.SystemClock;
+import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
@@ -33,6 +34,8 @@
  * @hide
  */
 public class StatLogger {
+    private static final String TAG = "StatLogger";
+
     private final Object mLock = new Object();
 
     private final int SIZE;
@@ -66,8 +69,12 @@
      */
     public void logDurationStat(int eventId, long start) {
         synchronized (mLock) {
-            mCountStats[eventId]++;
-            mDurationStats[eventId] += (getTime() - start);
+            if (eventId >= 0 && eventId < SIZE) {
+                mCountStats[eventId]++;
+                mDurationStats[eventId] += (getTime() - start);
+            } else {
+                Slog.wtf(TAG, "Invalid event ID: " + eventId);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 99f36d0..9db4e2b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -11316,8 +11316,11 @@
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                startLockTaskModeLocked(mStackSupervisor.anyTaskForIdLocked(taskId),
-                        true /* isSystemCaller */);
+                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+
+                // When starting lock task mode the stack must be in front and focused
+                task.getStack().moveToFront("startSystemLockTaskMode");
+                startLockTaskModeLocked(task, true /* isSystemCaller */);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -25860,6 +25863,14 @@
         public boolean isCallerRecents(int callingUid) {
             return getRecentTasks().isCallerRecents(callingUid);
         }
+
+        @Override
+        public boolean isUidActive(int uid) {
+            synchronized (ActivityManagerService.this) {
+                final UidRecord uidRec = mActiveUids.get(uid);
+                return (uidRec != null) && !uidRec.idle;
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
index 9c2ee87..fe65978 100644
--- a/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/GlobalSettingsToPropertiesMapper.java
@@ -42,6 +42,7 @@
         {Settings.Global.SYS_VDSO, "sys.vdso"},
         {Settings.Global.FPS_DEVISOR, ThreadedRenderer.DEBUG_FPS_DIVISOR},
         {Settings.Global.UID_CPUPOWER, "uid.cpupower"},
+        {Settings.Global.DISPLAY_PANEL_LPM, "sys.display_panel_lpm"},
     };
 
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 56d66de..8635cff 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -23,6 +23,7 @@
 import static android.os.Process.FIRST_APPLICATION_UID;
 
 import android.Manifest;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -240,6 +241,7 @@
     private static final int MSG_DYN_POLICY_MIX_STATE_UPDATE = 25;
     private static final int MSG_INDICATE_SYSTEM_READY = 26;
     private static final int MSG_ACCESSORY_PLUG_MEDIA_UNMUTE = 27;
+    private static final int MSG_NOTIFY_VOL_EVENT = 28;
     // start of messages handled under wakelock
     //   these messages can only be queued, i.e. sent with queueMsgUnderWakeLock(),
     //   and not with sendMsg(..., ..., SENDMSG_QUEUE, ...)
@@ -1334,11 +1336,9 @@
             extVolCtlr = mExtVolumeController;
         }
         if (extVolCtlr != null) {
-            try {
-                mExtVolumeController.notifyVolumeAdjust(direction);
-            } catch(RemoteException e) {
-                // nothing we can do about this. Do not log error, too much potential for spam
-            }
+            sendMsg(mAudioHandler, MSG_NOTIFY_VOL_EVENT, SENDMSG_QUEUE,
+                    direction, 0 /*ignored*/,
+                    extVolCtlr, 0 /*delay*/);
         } else {
             adjustSuggestedStreamVolume(direction, suggestedStreamType, flags, callingPackage,
                     caller, Binder.getCallingUid());
@@ -5054,6 +5054,15 @@
                     state);
         }
 
+        private void onNotifyVolumeEvent(@NonNull IAudioPolicyCallback apc,
+                @AudioManager.VolumeAdjustment int direction) {
+            try {
+                apc.notifyVolumeAdjust(direction);
+            } catch(Exception e) {
+                // nothing we can do about this. Do not log error, too much potential for spam
+            }
+        }
+
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
@@ -5216,6 +5225,10 @@
                 case MSG_DYN_POLICY_MIX_STATE_UPDATE:
                     onDynPolicyMixStateUpdate((String) msg.obj, msg.arg1);
                     break;
+
+                case MSG_NOTIFY_VOL_EVENT:
+                    onNotifyVolumeEvent((IAudioPolicyCallback) msg.obj, msg.arg1);
+                    break;
             }
         }
     }
diff --git a/services/core/java/com/android/server/content/SyncJobService.java b/services/core/java/com/android/server/content/SyncJobService.java
index 51499f7..9980930 100644
--- a/services/core/java/com/android/server/content/SyncJobService.java
+++ b/services/core/java/com/android/server/content/SyncJobService.java
@@ -137,11 +137,13 @@
                             + " params=" + jobParametersToString(params));
                 }
             } else if (runtime < 10 * 1000) {
-                // Job stopped too soon. WTF.
-                wtf("Job " + jobId + " stopped in " + runtime + " ms: "
-                        + " startUptime=" + startUptime
-                        + " nowUptime=" + nowUptime
-                        + " params=" + jobParametersToString(params));
+                // This happens too in a normal case too, and it's rather too often.
+                // Disable it for now.
+//                // Job stopped too soon. WTF.
+//                wtf("Job " + jobId + " stopped in " + runtime + " ms: "
+//                        + " startUptime=" + startUptime
+//                        + " nowUptime=" + nowUptime
+//                        + " params=" + jobParametersToString(params));
             }
 
             mStartedSyncs.delete(jobId);
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 401c05e..47a4fb2 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -40,7 +40,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.Intent.UriFlags;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
@@ -79,7 +78,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.DeviceIdleController;
 import com.android.server.FgThread;
-import com.android.server.ForceAppStandbyTracker;
+import com.android.server.AppStateTracker;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
 import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
@@ -184,7 +183,7 @@
     ActivityManagerInternal mActivityManagerInternal;
     IBatteryStats mBatteryStats;
     DeviceIdleController.LocalService mLocalDeviceIdleController;
-    final ForceAppStandbyTracker mForceAppStandbyTracker;
+    AppStateTracker mAppStateTracker;
 
     /**
      * Set to true once we are allowed to run third party apps.
@@ -787,20 +786,13 @@
     }
 
     /**
-     * Return whether an UID is in the foreground or not.
+     * Return whether an UID is active or idle.
      */
-    private boolean isUidInForeground(int uid) {
-        synchronized (mLock) {
-            if (mUidPriorityOverride.get(uid, 0) > 0) {
-                return true;
-            }
-        }
-        // Note UID observer may not be called in time, so we always check with the AM.
-        return mActivityManagerInternal.getUidProcessState(uid)
-                <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+    private boolean isUidActive(int uid) {
+        return mAppStateTracker.isUidActiveSynced(uid);
     }
 
-    private final Predicate<Integer> mIsUidInForegroundPredicate = this::isUidInForeground;
+    private final Predicate<Integer> mIsUidActivePredicate = this::isUidActive;
 
     public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
             int userId, String tag) {
@@ -826,7 +818,7 @@
 
                     // If any of work item is enqueued when the source is in the foreground,
                     // exempt the entire job.
-                    toCancel.maybeAddForegroundExemption(mIsUidInForegroundPredicate);
+                    toCancel.maybeAddForegroundExemption(mIsUidActivePredicate);
 
                     return JobScheduler.RESULT_SUCCESS;
                 }
@@ -838,7 +830,7 @@
             // Note if it's a sync job, this method is called on the handler so it's not exactly
             // the state when requestSync() was called, but that should be fine because of the
             // 1 minute foreground grace period.
-            jobStatus.maybeAddForegroundExemption(mIsUidInForegroundPredicate);
+            jobStatus.maybeAddForegroundExemption(mIsUidActivePredicate);
 
             if (DEBUG) Slog.d(TAG, "SCHEDULE: " + jobStatus.toShortString());
             // Jobs on behalf of others don't apply to the per-app job cap
@@ -1123,8 +1115,6 @@
         mDeviceIdleJobsController = DeviceIdleJobsController.get(this);
         mControllers.add(mDeviceIdleJobsController);
 
-        mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
-
         // If the job store determined that it can't yet reschedule persisted jobs,
         // we need to start watching the clock.
         if (!mJobs.jobTimesInflatedValid()) {
@@ -1185,7 +1175,8 @@
         if (PHASE_SYSTEM_SERVICES_READY == phase) {
             mConstants.start(getContext().getContentResolver());
 
-            mForceAppStandbyTracker.start();
+            mAppStateTracker = Preconditions.checkNotNull(
+                    LocalServices.getService(AppStateTracker.class));
 
             // Register br for package removals and user removals.
             final IntentFilter filter = new IntentFilter();
diff --git a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
index 5eb7700..e8057fb 100644
--- a/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -22,8 +22,10 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.server.ForceAppStandbyTracker;
-import com.android.server.ForceAppStandbyTracker.Listener;
+import com.android.internal.util.Preconditions;
+import com.android.server.AppStateTracker;
+import com.android.server.AppStateTracker.Listener;
+import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobStore;
 import com.android.server.job.StateControllerProto;
@@ -42,8 +44,7 @@
 
     private final JobSchedulerService mJobSchedulerService;
 
-    private final ForceAppStandbyTracker mForceAppStandbyTracker;
-
+    private final AppStateTracker mAppStateTracker;
 
     public static BackgroundJobsController get(JobSchedulerService service) {
         synchronized (sCreationLock) {
@@ -59,10 +60,9 @@
         super(service, context, lock);
         mJobSchedulerService = service;
 
-        mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
-
-        mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
-        mForceAppStandbyTracker.start();
+        mAppStateTracker = Preconditions.checkNotNull(
+                LocalServices.getService(AppStateTracker.class));
+        mAppStateTracker.addListener(mForceAppStandbyListener);
     }
 
     @Override
@@ -79,7 +79,7 @@
     public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
         pw.println("BackgroundJobsController");
 
-        mForceAppStandbyTracker.dump(pw, "");
+        mAppStateTracker.dump(pw, "");
 
         pw.println("Job state:");
         mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
@@ -92,16 +92,16 @@
             jobStatus.printUniqueId(pw);
             pw.print(" from ");
             UserHandle.formatUid(pw, uid);
-            pw.print(mForceAppStandbyTracker.isUidActive(uid) ? " active" : " idle");
-            if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) ||
-                    mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) {
+            pw.print(mAppStateTracker.isUidActive(uid) ? " active" : " idle");
+            if (mAppStateTracker.isUidPowerSaveWhitelisted(uid) ||
+                    mAppStateTracker.isUidTempPowerSaveWhitelisted(uid)) {
                 pw.print(", whitelisted");
             }
             pw.print(": ");
             pw.print(sourcePkg);
 
             pw.print(" [RUN_ANY_IN_BACKGROUND ");
-            pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(uid, sourcePkg)
+            pw.print(mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(uid, sourcePkg)
                     ? "allowed]" : "disallowed]");
 
             if ((jobStatus.satisfiedConstraints
@@ -118,7 +118,7 @@
         final long token = proto.start(fieldId);
         final long mToken = proto.start(StateControllerProto.BACKGROUND);
 
-        mForceAppStandbyTracker.dumpProto(proto,
+        mAppStateTracker.dumpProto(proto,
                 StateControllerProto.BackgroundJobsController.FORCE_APP_STANDBY_TRACKER);
 
         mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
@@ -136,14 +136,14 @@
             proto.write(TrackedJob.SOURCE_PACKAGE_NAME, sourcePkg);
 
             proto.write(TrackedJob.IS_IN_FOREGROUND,
-                    mForceAppStandbyTracker.isUidActive(sourceUid));
+                    mAppStateTracker.isUidActive(sourceUid));
             proto.write(TrackedJob.IS_WHITELISTED,
-                    mForceAppStandbyTracker.isUidPowerSaveWhitelisted(sourceUid) ||
-                    mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(sourceUid));
+                    mAppStateTracker.isUidPowerSaveWhitelisted(sourceUid) ||
+                    mAppStateTracker.isUidTempPowerSaveWhitelisted(sourceUid));
 
             proto.write(
                     TrackedJob.CAN_RUN_ANY_IN_BACKGROUND,
-                    mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(
+                    mAppStateTracker.isRunAnyInBackgroundAppOpsAllowed(
                             sourceUid, sourcePkg));
 
             proto.write(
@@ -197,7 +197,7 @@
         final int uid = jobStatus.getSourceUid();
         final String packageName = jobStatus.getSourcePackageName();
 
-        final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName,
+        final boolean canRun = !mAppStateTracker.areJobsRestricted(uid, packageName,
                 (jobStatus.getInternalFlags() & JobStatus.INTERNAL_FLAG_HAS_FOREGROUND_EXEMPTION)
                         != 0);
 
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 3a78f95..e5ff5b8 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -194,6 +194,9 @@
            UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
         init(userId);
         try {
+            // Try to see if the decryption key is still accessible before using the encryption key.
+            // The auth-bound decryption will be unrecoverable if the screen lock is disabled.
+            getDecryptKeyInternal(userId);
             return getEncryptKeyInternal(userId);
         } catch (UnrecoverableKeyException e) {
             Log.i(TAG, String.format(Locale.US,
@@ -219,7 +222,7 @@
            UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
         int generationId = getGenerationId(userId);
         String alias = getEncryptAlias(userId, generationId);
-        if (!mKeyStore.containsAlias(alias)) {
+        if (!isKeyLoaded(userId, generationId)) {
             throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias);
         }
         AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
@@ -268,7 +271,7 @@
            UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
         int generationId = getGenerationId(userId);
         String alias = getDecryptAlias(userId, generationId);
-        if (!mKeyStore.containsAlias(alias)) {
+        if (!isKeyLoaded(userId, generationId)) {
             throw new UnrecoverableKeyException("KeyStore doesn't contain key " + alias);
         }
         AndroidKeyStoreSecretKey key = (AndroidKeyStoreSecretKey) mKeyStore.getKey(
@@ -300,12 +303,12 @@
             return;
         }
         if (generationId == -1) {
-            Log.i(TAG, "Generating initial platform ID.");
+            Log.i(TAG, "Generating initial platform key generation ID.");
             generationId = 1;
         } else {
             Log.w(TAG, String.format(Locale.US, "Platform generation ID was %d but no "
                     + "entry was present in AndroidKeyStore. Generating fresh key.", generationId));
-            // Had to generate a fresh key, bump the generation id
+            // Have to generate a fresh key, so bump the generation id
             generationId++;
         }
 
@@ -374,7 +377,7 @@
         String decryptAlias = getDecryptAlias(userId, generationId);
         SecretKey secretKey = generateAesKey();
 
-        // Store Since decryption key first since it is more likely to fail.
+        // Store decryption key first since it is more likely to fail.
         mKeyStore.setEntry(
                 decryptAlias,
                 new KeyStore.SecretKeyEntry(secretKey),
@@ -386,7 +389,6 @@
                     .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
                     .setBoundToSpecificSecureUserId(userId)
                     .build());
-
         mKeyStore.setEntry(
                 encryptAlias,
                 new KeyStore.SecretKeyEntry(secretKey),
@@ -397,11 +399,7 @@
 
         setGenerationId(userId, generationId);
 
-        try {
-            secretKey.destroy();
-        } catch (DestroyFailedException e) {
-            Log.w(TAG, "Failed to destroy in-memory platform key.", e);
-        }
+        // TODO: Use a reliable way to destroy the temporary secretKey in memory.
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index fda6cdf..33e767fe 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -519,11 +519,11 @@
         byte[] locallyEncryptedKey;
         try {
             // TODO: Remove the extraneous logging here
-            Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()",
+            Log.d(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()",
                     sessionEntry.getKeyClaimant()));
-            Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()",
+            Log.d(TAG, constructLoggingMessage("sessionEntry.getVaultParams()",
                     sessionEntry.getVaultParams()));
-            Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse));
+            Log.d(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse));
             locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
                     sessionEntry.getKeyClaimant(),
                     sessionEntry.getVaultParams(),
@@ -543,9 +543,9 @@
 
         try {
             // TODO: Remove the extraneous logging here
-            Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()",
+            Log.d(TAG, constructLoggingMessage("sessionEntry.getLskfHash()",
                     sessionEntry.getLskfHash()));
-            Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey));
+            Log.d(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey));
             return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
         } catch (InvalidKeyException e) {
             Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e);
@@ -585,8 +585,8 @@
 
             try {
                 // TODO: Remove the extraneous logging here
-                Log.e(TAG, constructLoggingMessage("recoveryKey", recoveryKey));
-                Log.e(TAG, constructLoggingMessage("encryptedKeyMaterial", encryptedKeyMaterial));
+                Log.d(TAG, constructLoggingMessage("recoveryKey", recoveryKey));
+                Log.d(TAG, constructLoggingMessage("encryptedKeyMaterial", encryptedKeyMaterial));
                 byte[] keyMaterial =
                         KeySyncUtils.decryptApplicationKey(recoveryKey, encryptedKeyMaterial);
                 keyMaterialByAlias.put(alias, keyMaterial);
@@ -600,13 +600,16 @@
                 throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
                         "Failed to recover key with alias '" + alias + "': " + e.getMessage());
             } catch (AEADBadTagException e) {
-                // TODO: Remove the extraneous logging here
                 Log.e(TAG, "Got AEADBadTagException during decrypting application key with alias: "
                         + alias, e);
-                throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
-                        "Failed to recover key with alias '" + alias + "': " + e.getMessage());
+                // Ignore the exception to continue to recover the other application keys.
             }
         }
+        if (keyMaterialByAlias.isEmpty()) {
+            Log.e(TAG, "Failed to recover any of the application keys.");
+            throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
+                    "Failed to recover any of the application keys.");
+        }
         return keyMaterialByAlias;
     }
 
diff --git a/services/core/java/com/android/server/pm/InstantAppResolver.java b/services/core/java/com/android/server/pm/InstantAppResolver.java
index 6e898bb..bc9fa4b 100644
--- a/services/core/java/com/android/server/pm/InstantAppResolver.java
+++ b/services/core/java/com/android/server/pm/InstantAppResolver.java
@@ -126,17 +126,16 @@
         final Intent origIntent = requestObj.origIntent;
         final Intent sanitizedIntent = sanitizeIntent(origIntent);
 
-        final InstantAppDigest digest = getInstantAppDigest(origIntent);
-        final int[] shaPrefix = digest.getDigestPrefix();
         AuxiliaryResolveInfo resolveInfo = null;
         @ResolutionStatus int resolutionStatus = RESOLUTION_SUCCESS;
         try {
             final List<InstantAppResolveInfo> instantAppResolveInfoList =
-                    connection.getInstantAppResolveInfoList(sanitizedIntent, shaPrefix, token);
+                    connection.getInstantAppResolveInfoList(sanitizedIntent,
+                            requestObj.digest.getDigestPrefixSecure(), token);
             if (instantAppResolveInfoList != null && instantAppResolveInfoList.size() > 0) {
                 resolveInfo = InstantAppResolver.filterInstantAppIntent(
                         instantAppResolveInfoList, origIntent, requestObj.resolvedType,
-                        requestObj.userId, origIntent.getPackage(), digest, token);
+                        requestObj.userId, origIntent.getPackage(), requestObj.digest, token);
             }
         } catch (ConnectionException e) {
             if (e.failure == ConnectionException.FAILURE_BIND) {
@@ -166,12 +165,6 @@
         return resolveInfo;
     }
 
-    private static InstantAppDigest getInstantAppDigest(Intent origIntent) {
-        return origIntent.getData() != null && !TextUtils.isEmpty(origIntent.getData().getHost())
-                ? new InstantAppDigest(origIntent.getData().getHost(), 5 /*maxDigests*/)
-                : InstantAppDigest.UNDEFINED;
-    }
-
     public static void doInstantAppResolutionPhaseTwo(Context context,
             InstantAppResolverConnection connection, InstantAppRequest requestObj,
             ActivityInfo instantAppInstaller, Handler callbackHandler) {
@@ -182,8 +175,6 @@
         }
         final Intent origIntent = requestObj.origIntent;
         final Intent sanitizedIntent = sanitizeIntent(origIntent);
-        final InstantAppDigest digest = getInstantAppDigest(origIntent);
-        final int[] shaPrefix = digest.getDigestPrefix();
 
         final PhaseTwoCallback callback = new PhaseTwoCallback() {
             @Override
@@ -194,7 +185,8 @@
                     final AuxiliaryResolveInfo instantAppIntentInfo =
                             InstantAppResolver.filterInstantAppIntent(
                                     instantAppResolveInfoList, origIntent, null /*resolvedType*/,
-                                    0 /*userId*/, origIntent.getPackage(), digest, token);
+                                    0 /*userId*/, origIntent.getPackage(), requestObj.digest,
+                                    token);
                     if (instantAppIntentInfo != null) {
                         failureIntent = instantAppIntentInfo.failureIntent;
                     } else {
@@ -225,8 +217,9 @@
             }
         };
         try {
-            connection.getInstantAppIntentFilterList(sanitizedIntent, shaPrefix, token, callback,
-                    callbackHandler, startTime);
+            connection.getInstantAppIntentFilterList(sanitizedIntent,
+                    requestObj.digest.getDigestPrefixSecure(), token, callback, callbackHandler,
+                    startTime);
         } catch (ConnectionException e) {
             @ResolutionStatus int resolutionStatus = RESOLUTION_FAILURE;
             if (e.failure == ConnectionException.FAILURE_BIND) {
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 842f8d0..23185d7 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -122,7 +122,8 @@
             UserManager.DISALLOW_CONFIG_BRIGHTNESS,
             UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE,
             UserManager.DISALLOW_AMBIENT_DISPLAY,
-            UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT
+            UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT,
+            UserManager.DISALLOW_PRINTING
     });
 
     /**
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 177d6af..0502848 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5603,7 +5603,9 @@
         final int fl = PolicyControl.getWindowFlags(null,
                 mTopFullscreenOpaqueWindowState.getAttrs());
         if (localLOGV) {
-            Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw());
+            Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw()
+                    + " shown position: "
+                    + mTopFullscreenOpaqueWindowState.getShownPositionLw());
             Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()
                     + " lp.flags=0x" + Integer.toHexString(fl));
         }
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 3af3fcb..e9c4c5c 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -232,6 +232,14 @@
         public Rect getFrameLw();
 
         /**
+         * Retrieve the current position of the window that is actually shown.
+         * Must be called with the window manager lock held.
+         *
+         * @return Point The point holding the shown window position.
+         */
+        public Point getShownPositionLw();
+
+        /**
          * Retrieve the frame of the display that this window was last
          * laid out in.  Must be called with the
          * window manager lock held.
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index c280739..a87ae1e 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -284,7 +284,7 @@
         }
     }
 
-    private final static class AnomalyAlarmReceiver extends BroadcastReceiver {
+    public final static class AnomalyAlarmReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred.");
@@ -304,7 +304,7 @@
         }
     }
 
-    private final static class PullingAlarmReceiver extends BroadcastReceiver {
+    public final static class PullingAlarmReceiver extends BroadcastReceiver {
       @Override
       public void onReceive(Context context, Intent intent) {
         if (DEBUG)
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index da3a035..f2ad6fb 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -272,8 +272,6 @@
     }
 
     boolean updateWallpaperOffset(WindowState wallpaperWin, int dw, int dh, boolean sync) {
-        int xOffset = 0;
-        int yOffset = 0;
         boolean rawChanged = false;
         // Set the default wallpaper x-offset to either edge of the screen (depending on RTL), to
         // match the behavior of most Launchers
@@ -285,8 +283,11 @@
         if (mLastWallpaperDisplayOffsetX != Integer.MIN_VALUE) {
             offset += mLastWallpaperDisplayOffsetX;
         }
-        xOffset = offset;
-
+        boolean changed = wallpaperWin.mXOffset != offset;
+        if (changed) {
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " x: " + offset);
+            wallpaperWin.mXOffset = offset;
+        }
         if (wallpaperWin.mWallpaperX != wpx || wallpaperWin.mWallpaperXStep != wpxs) {
             wallpaperWin.mWallpaperX = wpx;
             wallpaperWin.mWallpaperXStep = wpxs;
@@ -300,16 +301,17 @@
         if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
             offset += mLastWallpaperDisplayOffsetY;
         }
-        yOffset = offset;
-
+        if (wallpaperWin.mYOffset != offset) {
+            if (DEBUG_WALLPAPER) Slog.v(TAG, "Update wallpaper " + wallpaperWin + " y: " + offset);
+            changed = true;
+            wallpaperWin.mYOffset = offset;
+        }
         if (wallpaperWin.mWallpaperY != wpy || wallpaperWin.mWallpaperYStep != wpys) {
             wallpaperWin.mWallpaperY = wpy;
             wallpaperWin.mWallpaperYStep = wpys;
             rawChanged = true;
         }
 
-        boolean changed = wallpaperWin.mWinAnimator.setWallpaperOffset(xOffset, yOffset);
-
         if (rawChanged && (wallpaperWin.mAttrs.privateFlags &
                 WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) != 0) {
             try {
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index ddda027..2ae5c7b 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -74,6 +74,10 @@
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
             final WindowState wallpaper = mChildren.get(wallpaperNdx);
             if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) {
+                final WindowStateAnimator winAnimator = wallpaper.mWinAnimator;
+                winAnimator.computeShownFrameLocked();
+                // No need to lay out the windows - we can just set the wallpaper position directly.
+                winAnimator.setWallpaperOffset(wallpaper.mShownPosition);
                 // We only want to be synchronous with one wallpaper.
                 sync = false;
             }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 48d29e3..240e7fd 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -137,6 +137,7 @@
 import static com.android.server.wm.proto.WindowStateProto.REMOVE_ON_EXIT;
 import static com.android.server.wm.proto.WindowStateProto.REQUESTED_HEIGHT;
 import static com.android.server.wm.proto.WindowStateProto.REQUESTED_WIDTH;
+import static com.android.server.wm.proto.WindowStateProto.SHOWN_POSITION;
 import static com.android.server.wm.proto.WindowStateProto.STABLE_INSETS;
 import static com.android.server.wm.proto.WindowStateProto.STACK_ID;
 import static com.android.server.wm.proto.WindowStateProto.SURFACE_INSETS;
@@ -296,6 +297,12 @@
     private final MergedConfiguration mLastReportedConfiguration = new MergedConfiguration();
 
     /**
+     * Actual position of the surface shown on-screen (may be modified by animation). These are
+     * in the screen's coordinate space (WITH the compatibility scale applied).
+     */
+    final Point mShownPosition = new Point();
+
+    /**
      * Insets that determine the actually visible area.  These are in the application's
      * coordinate space (without compatibility scale applied).
      */
@@ -454,6 +461,10 @@
     int mWallpaperDisplayOffsetX = Integer.MIN_VALUE;
     int mWallpaperDisplayOffsetY = Integer.MIN_VALUE;
 
+    // Wallpaper windows: pixels offset based on above variables.
+    int mXOffset;
+    int mYOffset;
+
     /**
      * This is set after IWindowSession.relayout() has been called at
      * least once for the window.  It allows us to detect the situation
@@ -734,6 +745,8 @@
         mRequestedHeight = 0;
         mLastRequestedWidth = 0;
         mLastRequestedHeight = 0;
+        mXOffset = 0;
+        mYOffset = 0;
         mLayer = 0;
         mInputWindowHandle = new InputWindowHandle(
                 mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, c,
@@ -1099,6 +1112,11 @@
     }
 
     @Override
+    public Point getShownPositionLw() {
+        return mShownPosition;
+    }
+
+    @Override
     public Rect getDisplayFrameLw() {
         return mDisplayFrame;
     }
@@ -3116,6 +3134,7 @@
         mContentInsets.writeToProto(proto, CONTENT_INSETS);
         mAttrs.surfaceInsets.writeToProto(proto, SURFACE_INSETS);
         mSurfacePosition.writeToProto(proto, SURFACE_POSITION);
+        mShownPosition.writeToProto(proto, SHOWN_POSITION);
         mWinAnimator.writeToProto(proto, ANIMATOR);
         proto.write(ANIMATING_EXIT, mAnimatingExit);
         for (int i = 0; i < mChildren.size(); i++) {
@@ -3231,6 +3250,10 @@
             pw.print(prefix); pw.print("mRelayoutCalled="); pw.print(mRelayoutCalled);
                     pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded);
         }
+        if (mXOffset != 0 || mYOffset != 0) {
+            pw.print(prefix); pw.print("Offsets x="); pw.print(mXOffset);
+                    pw.print(" y="); pw.println(mYOffset);
+        }
         if (dumpAll) {
             pw.print(prefix); pw.print("mGivenContentInsets=");
                     mGivenContentInsets.printShortString(pw);
@@ -3249,6 +3272,7 @@
                     pw.println(getLastReportedConfiguration());
         }
         pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
+                pw.print(" mShownPosition="); mShownPosition.printShortString(pw);
                 pw.print(" isReadyForDisplay()="); pw.print(isReadyForDisplay());
                 pw.print(" mWindowRemovalAllowed="); pw.println(mWindowRemovalAllowed);
         if (dumpAll) {
@@ -4171,8 +4195,9 @@
         final int width = mFrame.width();
         final int height = mFrame.height();
 
-        final int left = mFrame.left;
-        final int top = mFrame.top;
+        // Compute the offset of the window in relation to the decor rect.
+        final int left = mXOffset + mFrame.left;
+        final int top = mYOffset + mFrame.top;
 
         // Initialize the decor rect to the entire frame.
         if (isDockedResizing()) {
@@ -4367,8 +4392,8 @@
         float9[Matrix.MSKEW_Y] = mWinAnimator.mDtDx;
         float9[Matrix.MSKEW_X] = mWinAnimator.mDtDy;
         float9[Matrix.MSCALE_Y] = mWinAnimator.mDsDy;
-        int x = mSurfacePosition.x;
-        int y = mSurfacePosition.y;
+        int x = mSurfacePosition.x + mShownPosition.x;
+        int y = mSurfacePosition.y + mShownPosition.y;
 
         // If changed, also adjust transformFrameToSurfacePosition
         final WindowContainer parent = getParent();
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 499322c..dd23b6f 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -209,14 +209,10 @@
     float mExtraHScale = (float) 1.0;
     float mExtraVScale = (float) 1.0;
 
-    // An offset in pixel of the surface contents from the window position. Used for Wallpaper
-    // to provide the effect of scrolling within a large surface. We just use these values as
-    // a cache.
-    int mXOffset = 0;
-    int mYOffset = 0;
-
     private final Rect mTmpSize = new Rect();
 
+    private final SurfaceControl.Transaction mReparentTransaction = new SurfaceControl.Transaction();
+
     WindowStateAnimator(final WindowState win) {
         final WindowManagerService service = win.mService;
 
@@ -377,9 +373,9 @@
                 // child layers need to be reparented to the new surface to make this
                 // transparent to the app.
                 if (mWin.mAppToken == null || mWin.mAppToken.isRelaunching() == false) {
-                    SurfaceControl.openTransaction();
-                    mPendingDestroySurface.reparentChildrenInTransaction(mSurfaceController);
-                    SurfaceControl.closeTransaction();
+                    mReparentTransaction.reparentChildren(mPendingDestroySurface.mSurfaceControl,
+                            mSurfaceController.mSurfaceControl.getHandle())
+                            .apply();
                 }
             }
         }
@@ -442,7 +438,7 @@
             flags |= SurfaceControl.SECURE;
         }
 
-        mTmpSize.set(0, 0, 0, 0);
+        mTmpSize.set(w.mFrame.left + w.mXOffset, w.mFrame.top + w.mYOffset, 0, 0);
         calculateSurfaceBounds(w, attrs);
         final int width = mTmpSize.width();
         final int height = mTmpSize.height();
@@ -593,7 +589,7 @@
                         if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
                             WindowManagerService.logSurface(mWin, "DESTROY PENDING", true);
                         }
-                        mPendingDestroySurface.destroyInTransaction();
+                        mPendingDestroySurface.destroyNotInTransaction();
                     }
                     mPendingDestroySurface = mSurfaceController;
                 }
@@ -630,7 +626,7 @@
                 if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
                     WindowManagerService.logSurface(mWin, "DESTROY PENDING", true);
                 }
-                mPendingDestroySurface.destroyInTransaction();
+                mPendingDestroySurface.destroyNotInTransaction();
                 // Don't hide wallpaper if we're destroying a deferred surface
                 // after a surface mode change.
                 if (!mDestroyPreservedSurfaceUponRedraw) {
@@ -683,8 +679,8 @@
 
             // WindowState.prepareSurfaces expands for surface insets (in order they don't get
             // clipped by the WindowState surface), so we need to go into the other direction here.
-            tmpMatrix.postTranslate(mWin.mAttrs.surfaceInsets.left,
-                    mWin.mAttrs.surfaceInsets.top);
+            tmpMatrix.postTranslate(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left,
+                    mWin.mYOffset + mWin.mAttrs.surfaceInsets.top);
 
 
             // "convert" it into SurfaceFlinger's format
@@ -699,6 +695,9 @@
             mDtDx = tmpFloats[Matrix.MSKEW_Y];
             mDtDy = tmpFloats[Matrix.MSKEW_X];
             mDsDy = tmpFloats[Matrix.MSCALE_Y];
+            float x = tmpFloats[Matrix.MTRANS_X];
+            float y = tmpFloats[Matrix.MTRANS_Y];
+            mWin.mShownPosition.set(Math.round(x), Math.round(y));
 
             // Now set the alpha...  but because our current hardware
             // can't do alpha transformation on a non-opaque surface,
@@ -708,7 +707,8 @@
             mShownAlpha = mAlpha;
             if (!mService.mLimitedAlphaCompositing
                     || (!PixelFormat.formatHasAlpha(mWin.mAttrs.format)
-                    || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy)))) {
+                    || (mWin.isIdentityMatrix(mDsDx, mDtDx, mDtDy, mDsDy)
+                            && x == frame.left && y == frame.top))) {
                 //Slog.i(TAG_WM, "Applying alpha transform");
                 if (screenAnimation) {
                     mShownAlpha *= screenRotationAnimation.getEnterTransformation().getAlpha();
@@ -738,6 +738,10 @@
                 TAG, "computeShownFrameLocked: " + this +
                 " not attached, mAlpha=" + mAlpha);
 
+        // WindowState.prepareSurfaces expands for surface insets (in order they don't get
+        // clipped by the WindowState surface), so we need to go into the other direction here.
+        mWin.mShownPosition.set(mWin.mXOffset + mWin.mAttrs.surfaceInsets.left,
+                mWin.mYOffset + mWin.mAttrs.surfaceInsets.top);
         mShownAlpha = mAlpha;
         mHaveMatrix = false;
         mDsDx = mWin.mGlobalScale;
@@ -788,6 +792,12 @@
         if (DEBUG_WINDOW_CROP) Slog.d(TAG, "win=" + w + " Initial clip rect: " + clipRect
                 + " fullscreen=" + fullscreen);
 
+        if (isFreeformResizing && !w.isChildWindow()) {
+            // For freeform resizing non child windows, we are using the big surface positioned
+            // at 0,0. Thus we must express the crop in that coordinate space.
+            clipRect.offset(w.mShownPosition.x, w.mShownPosition.y);
+        }
+
         w.expandForSurfaceInsets(clipRect);
 
         // The clip rect was generated assuming (0,0) as the window origin,
@@ -829,7 +839,7 @@
             return;
         }
 
-        mTmpSize.set(0, 0, 0, 0);
+        mTmpSize.set(w.mShownPosition.x, w.mShownPosition.y, 0, 0);
         calculateSurfaceBounds(w, attrs);
 
         mExtraHScale = (float) 1.0;
@@ -963,6 +973,11 @@
             // then take over the scaling until the new buffer arrives, and things
             // will be seamless.
             mForceScaleUntilResize = true;
+        } else {
+            if (!w.mSeamlesslyRotated) {
+                mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top,
+                        recoveringMemory);
+            }
         }
 
         // If we are ending the scaling mode. We switch to SCALING_MODE_FREEZE
@@ -1154,26 +1169,24 @@
         mSurfaceController.setTransparentRegionHint(region);
     }
 
-    boolean setWallpaperOffset(int dx, int dy) {
-        if (mXOffset == dx && mYOffset == dy) {
-            return false;
-        }
-        mXOffset = dx;
-        mYOffset = dy;
+    void setWallpaperOffset(Point shownPosition) {
+        final LayoutParams attrs = mWin.getAttrs();
+        final int left = shownPosition.x - attrs.surfaceInsets.left;
+        final int top = shownPosition.y - attrs.surfaceInsets.top;
 
         try {
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setWallpaperOffset");
             mService.openSurfaceTransaction();
-            mSurfaceController.setPositionInTransaction(dx, dy, false);
+            mSurfaceController.setPositionInTransaction(mWin.mFrame.left + left,
+                    mWin.mFrame.top + top, false);
             applyCrop(null, false);
         } catch (RuntimeException e) {
             Slog.w(TAG, "Error positioning surface of " + mWin
-                    + " pos=(" + dx + "," + dy + ")", e);
+                    + " pos=(" + left + "," + top + ")", e);
         } finally {
             mService.closeSurfaceTransaction("setWallpaperOffset");
             if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                     "<<< CLOSE TRANSACTION setWallpaperOffset");
-            return true;
         }
     }
 
@@ -1408,7 +1421,7 @@
     void destroySurface() {
         try {
             if (mSurfaceController != null) {
-                mSurfaceController.destroyInTransaction();
+                mSurfaceController.destroyNotInTransaction();
             }
         } catch (RuntimeException e) {
             Slog.w(TAG, "Exception thrown when destroying surface " + this
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 2f38556..554a600 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -168,7 +168,7 @@
         }
     }
 
-    void destroyInTransaction() {
+    void destroyNotInTransaction() {
         if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
             Slog.i(TAG, "Destroying surface " + this + " called by " + Debug.getCallers(8));
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index c4e485c..3557dc9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -122,16 +122,6 @@
     }
 
     @Override
-    public void setPrintingEnabled(ComponentName admin, boolean enabled) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isPrintingEnabled() {
-        return true;
-    }
-
-    @Override
     public List<String> setMeteredDataDisabled(ComponentName admin, List<String> packageNames) {
         return packageNames;
     }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 4afe1c7..953a79f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -305,8 +305,6 @@
 
     private static final String TAG_PASSWORD_VALIDITY = "password-validity";
 
-    private static final String TAG_PRINTING_ENABLED = "printing-enabled";
-
     private static final String TAG_TRANSFER_OWNERSHIP_BUNDLE = "transfer-ownership-bundle";
 
     private static final int REQUEST_EXPIRE_PASSWORD = 5571;
@@ -615,8 +613,6 @@
 
         long mPasswordTokenHandle = 0;
 
-        boolean mPrintingEnabled = true;
-
         public DevicePolicyData(int userHandle) {
             mUserHandle = userHandle;
         }
@@ -2948,12 +2944,6 @@
                 out.endTag(null, TAG_CURRENT_INPUT_METHOD_SET);
             }
 
-            if (!policy.mPrintingEnabled) {
-                out.startTag(null, TAG_PRINTING_ENABLED);
-                out.attribute(null, ATTR_VALUE, Boolean.toString(policy.mPrintingEnabled));
-                out.endTag(null, TAG_PRINTING_ENABLED);
-            }
-
             for (final String cert : policy.mOwnerInstalledCaCerts) {
                 out.startTag(null, TAG_OWNER_INSTALLED_CA_CERT);
                 out.attribute(null, ATTR_ALIAS, cert);
@@ -3172,9 +3162,6 @@
                     policy.mCurrentInputMethodSet = true;
                 } else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
                     policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
-                } else if (TAG_PRINTING_ENABLED.equals(tag)) {
-                    String enabled = parser.getAttributeValue(null, ATTR_VALUE);
-                    policy.mPrintingEnabled = Boolean.toString(true).equals(enabled);
                 } else {
                     Slog.w(LOG_TAG, "Unknown tag: " + tag);
                     XmlUtils.skipCurrentTag(parser);
@@ -10417,7 +10404,7 @@
         public CharSequence getPrintingDisabledReasonForUser(@UserIdInt int userId) {
             synchronized (DevicePolicyManagerService.this) {
                 DevicePolicyData policy = getUserData(userId);
-                if (policy.mPrintingEnabled) {
+                if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_PRINTING)) {
                     Log.e(LOG_TAG, "printing is enabled");
                     return null;
                 }
@@ -12789,27 +12776,6 @@
         }
     }
 
-    private boolean hasPrinting() {
-        return mInjector.getPackageManager().hasSystemFeature(PackageManager.FEATURE_PRINTING);
-    }
-
-    @Override
-    public void setPrintingEnabled(ComponentName admin, boolean enabled) {
-        if (!mHasFeature || !hasPrinting()) {
-            return;
-        }
-        Preconditions.checkNotNull(admin, "Admin cannot be null.");
-        enforceProfileOrDeviceOwner(admin);
-        synchronized (this) {
-            final int userHandle = mInjector.userHandleGetCallingUserId();
-            DevicePolicyData policy = getUserData(userHandle);
-            if (policy.mPrintingEnabled != enabled) {
-                policy.mPrintingEnabled = enabled;
-                saveSettingsLocked(userHandle);
-            }
-        }
-    }
-
     private void deleteTransferOwnershipMetadataFileLocked() {
         mTransferOwnershipMetadataManager.deleteMetadataFile();
     }
@@ -12839,25 +12805,6 @@
         }
     }
 
-    /**
-     * Returns whether printing is enabled for current user.
-     * @hide
-     */
-    @Override
-    public boolean isPrintingEnabled() {
-        if (!hasPrinting()) {
-            return false;
-        }
-        if (!mHasFeature) {
-            return true;
-        }
-        synchronized (this) {
-            final int userHandle = mInjector.userHandleGetCallingUserId();
-            DevicePolicyData policy = getUserData(userHandle);
-            return policy.mPrintingEnabled;
-        }
-    }
-
     @Override
     public int addOverrideApn(@NonNull ComponentName who, @NonNull ApnSetting apnSetting) {
         if (!mHasFeature) {
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index e1c1eb2..e8620ed 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -21,7 +21,6 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
-import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
 import android.content.ComponentName;
 import android.content.Context;
@@ -115,12 +114,9 @@
 
         private final SparseArray<UserState> mUserStates = new SparseArray<>();
 
-        private final DevicePolicyManager mDpm;
-
         PrintManagerImpl(Context context) {
             mContext = context;
             mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
-            mDpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
             registerContentObservers();
             registerBroadcastReceivers();
         }
@@ -722,7 +718,7 @@
         }
 
         private boolean isPrintingEnabled() {
-            return mDpm == null || mDpm.isPrintingEnabled();
+            return !mUserManager.hasUserRestriction(UserManager.DISALLOW_PRINTING);
         }
 
         private void dump(@NonNull DualDumpOutputStream dumpStream,
diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
similarity index 92%
rename from services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
rename to services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
index 2c50f22..90db2a3 100644
--- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.server;
 
-import static com.android.server.ForceAppStandbyTracker.TARGET_OP;
+import static com.android.server.AppStateTracker.TARGET_OP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.app.AppOpsManager.OpEntry;
 import android.app.AppOpsManager.PackageOps;
@@ -63,7 +64,7 @@
 
 import com.android.internal.app.IAppOpsCallback;
 import com.android.internal.app.IAppOpsService;
-import com.android.server.ForceAppStandbyTracker.Listener;
+import com.android.server.AppStateTracker.Listener;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -82,17 +83,17 @@
 import java.util.function.Consumer;
 
 /**
- * Tests for {@link ForceAppStandbyTracker}
+ * Tests for {@link AppStateTracker}
  *
  * Run with:
- atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AppStateTrackerTest.java
  */
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class ForceAppStandbyTrackerTest {
+public class AppStateTrackerTest {
 
-    private class ForceAppStandbyTrackerTestable extends ForceAppStandbyTracker {
-        ForceAppStandbyTrackerTestable() {
+    private class AppStateTrackerTestable extends AppStateTracker {
+        AppStateTrackerTestable() {
             super(mMockContext, Looper.getMainLooper());
         }
 
@@ -112,6 +113,11 @@
         }
 
         @Override
+        ActivityManagerInternal injectActivityManagerInternal() {
+            return mMockIActivityManagerInternal;
+        }
+
+        @Override
         PowerManagerInternal injectPowerManagerInternal() {
             return mMockPowerManagerInternal;
         }
@@ -152,6 +158,9 @@
     private IActivityManager mMockIActivityManager;
 
     @Mock
+    private ActivityManagerInternal mMockIActivityManagerInternal;
+
+    @Mock
     private AppOpsManager mMockAppOpsManager;
 
     @Mock
@@ -195,7 +204,7 @@
         return new PowerSaveState.Builder().setBatterySaverEnabled(mPowerSaveMode).build();
     }
 
-    private ForceAppStandbyTrackerTestable newInstance() throws Exception {
+    private AppStateTrackerTestable newInstance() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         when(mMockIAppOpsService.checkOperation(eq(TARGET_OP), anyInt(), anyString()))
@@ -205,12 +214,12 @@
                             AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED;
                 });
 
-        final ForceAppStandbyTrackerTestable instance = new ForceAppStandbyTrackerTestable();
+        final AppStateTrackerTestable instance = new AppStateTrackerTestable();
 
         return instance;
     }
 
-    private void callStart(ForceAppStandbyTrackerTestable instance) throws RemoteException {
+    private void callStart(AppStateTrackerTestable instance) throws RemoteException {
 
         // Set up functions that start() calls.
         when(mMockPowerManagerInternal.getLowPowerState(eq(ServiceType.FORCE_ALL_APPS_STANDBY)))
@@ -223,7 +232,7 @@
         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
 
         // Call start.
-        instance.start();
+        instance.onSystemServicesReady();
 
         // Capture the listeners.
         ArgumentCaptor<IUidObserver> uidObserverArgumentCaptor =
@@ -287,7 +296,7 @@
     private static final int JOBS_ONLY = 1 << 1;
     private static final int JOBS_AND_ALARMS = ALARMS_ONLY | JOBS_ONLY;
 
-    private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName,
+    private void areRestricted(AppStateTrackerTestable instance, int uid, String packageName,
             int restrictionTypes, boolean exemptFromBatterySaver) {
         assertEquals(((restrictionTypes & JOBS_ONLY) != 0),
                 instance.areJobsRestricted(uid, packageName, exemptFromBatterySaver));
@@ -295,13 +304,13 @@
                 instance.areAlarmsRestricted(uid, packageName, exemptFromBatterySaver));
     }
 
-    private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName,
+    private void areRestricted(AppStateTrackerTestable instance, int uid, String packageName,
             int restrictionTypes) {
         areRestricted(instance, uid, packageName, restrictionTypes,
                 /*exemptFromBatterySaver=*/ false);
     }
 
-    private void areRestrictedWithExemption(ForceAppStandbyTrackerTestable instance,
+    private void areRestrictedWithExemption(AppStateTrackerTestable instance,
             int uid, String packageName, int restrictionTypes) {
         areRestricted(instance, uid, packageName, restrictionTypes,
                 /*exemptFromBatterySaver=*/ true);
@@ -309,7 +318,7 @@
 
     @Test
     public void testAll() throws Exception {
-        final ForceAppStandbyTrackerTestable instance = newInstance();
+        final AppStateTrackerTestable instance = newInstance();
         callStart(instance);
 
         assertFalse(instance.isForceAllAppsStandbyEnabled());
@@ -343,6 +352,7 @@
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
         mIUidObserver.onUidActive(UID_1);
+        waitUntilMainHandlerDrain();
         areRestricted(instance, UID_1, PACKAGE_1, NONE);
         areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
         areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
@@ -350,6 +360,7 @@
         assertFalse(instance.isUidActive(UID_2));
 
         mIUidObserver.onUidGone(UID_1, /*disable=*/ false);
+        waitUntilMainHandlerDrain();
         areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
         areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
         areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
@@ -357,11 +368,13 @@
         assertFalse(instance.isUidActive(UID_2));
 
         mIUidObserver.onUidActive(UID_1);
+        waitUntilMainHandlerDrain();
         areRestricted(instance, UID_1, PACKAGE_1, NONE);
         areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
         areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
 
         mIUidObserver.onUidIdle(UID_1, /*disable=*/ false);
+        waitUntilMainHandlerDrain();
         areRestricted(instance, UID_1, PACKAGE_1, JOBS_AND_ALARMS);
         areRestricted(instance, UID_2, PACKAGE_2, JOBS_AND_ALARMS);
         areRestricted(instance, Process.SYSTEM_UID, PACKAGE_SYSTEM, NONE);
@@ -462,15 +475,20 @@
 
     @Test
     public void testUidStateForeground() throws Exception {
-        final ForceAppStandbyTrackerTestable instance = newInstance();
+        final AppStateTrackerTestable instance = newInstance();
         callStart(instance);
 
         mIUidObserver.onUidActive(UID_1);
 
+        waitUntilMainHandlerDrain();
         assertTrue(instance.isUidActive(UID_1));
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
+        assertTrue(instance.isUidActiveSynced(UID_1));
+        assertFalse(instance.isUidActiveSynced(UID_2));
+        assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
+
         assertFalse(instance.isUidInForeground(UID_1));
         assertFalse(instance.isUidInForeground(UID_2));
         assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
@@ -479,10 +497,15 @@
         mIUidObserver.onUidStateChanged(UID_2,
                 ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE, 0);
 
+        waitUntilMainHandlerDrain();
         assertTrue(instance.isUidActive(UID_1));
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
+        assertTrue(instance.isUidActiveSynced(UID_1));
+        assertFalse(instance.isUidActiveSynced(UID_2));
+        assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
+
         assertFalse(instance.isUidInForeground(UID_1));
         assertTrue(instance.isUidInForeground(UID_2));
         assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
@@ -491,6 +514,7 @@
         mIUidObserver.onUidStateChanged(UID_1,
                 ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE, 0);
 
+        waitUntilMainHandlerDrain();
         assertTrue(instance.isUidActive(UID_1));
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
@@ -501,6 +525,7 @@
 
         mIUidObserver.onUidGone(UID_1, true);
 
+        waitUntilMainHandlerDrain();
         assertFalse(instance.isUidActive(UID_1));
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
@@ -511,6 +536,7 @@
 
         mIUidObserver.onUidIdle(UID_2, true);
 
+        waitUntilMainHandlerDrain();
         assertFalse(instance.isUidActive(UID_1));
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
@@ -522,6 +548,7 @@
         mIUidObserver.onUidStateChanged(UID_1,
                 ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0);
 
+        waitUntilMainHandlerDrain();
         assertFalse(instance.isUidActive(UID_1));
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
@@ -533,18 +560,39 @@
         mIUidObserver.onUidStateChanged(UID_1,
                 ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0);
 
+        waitUntilMainHandlerDrain();
         assertFalse(instance.isUidActive(UID_1));
         assertFalse(instance.isUidActive(UID_2));
         assertTrue(instance.isUidActive(Process.SYSTEM_UID));
 
+        assertFalse(instance.isUidActiveSynced(UID_1));
+        assertFalse(instance.isUidActiveSynced(UID_2));
+        assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
+
         assertFalse(instance.isUidInForeground(UID_1));
         assertFalse(instance.isUidInForeground(UID_2));
         assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
+
+        // The result from AMI.isUidActive() only affects isUidActiveSynced().
+        when(mMockIActivityManagerInternal.isUidActive(anyInt())).thenReturn(true);
+
+        assertFalse(instance.isUidActive(UID_1));
+        assertFalse(instance.isUidActive(UID_2));
+        assertTrue(instance.isUidActive(Process.SYSTEM_UID));
+
+        assertTrue(instance.isUidActiveSynced(UID_1));
+        assertTrue(instance.isUidActiveSynced(UID_2));
+        assertTrue(instance.isUidActiveSynced(Process.SYSTEM_UID));
+
+        assertFalse(instance.isUidInForeground(UID_1));
+        assertFalse(instance.isUidInForeground(UID_2));
+        assertTrue(instance.isUidInForeground(Process.SYSTEM_UID));
+
     }
 
     @Test
     public void testExempt() throws Exception {
-        final ForceAppStandbyTrackerTestable instance = newInstance();
+        final AppStateTrackerTestable instance = newInstance();
         callStart(instance);
 
         assertFalse(instance.isForceAllAppsStandbyEnabled());
@@ -610,7 +658,7 @@
     }
 
     public void loadPersistedAppOps() throws Exception {
-        final ForceAppStandbyTrackerTestable instance = newInstance();
+        final AppStateTrackerTestable instance = newInstance();
 
         final List<PackageOps> ops = new ArrayList<>();
 
@@ -620,7 +668,7 @@
                 AppOpsManager.OP_ACCESS_NOTIFICATIONS,
                 AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
         entries.add(new AppOpsManager.OpEntry(
-                ForceAppStandbyTracker.TARGET_OP,
+                AppStateTracker.TARGET_OP,
                 AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
 
         ops.add(new PackageOps(PACKAGE_1, UID_1, entries));
@@ -628,7 +676,7 @@
         //--------------------------------------------------
         entries = new ArrayList<>();
         entries.add(new AppOpsManager.OpEntry(
-                ForceAppStandbyTracker.TARGET_OP,
+                AppStateTracker.TARGET_OP,
                 AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
 
         ops.add(new PackageOps(PACKAGE_2, UID_2, entries));
@@ -636,7 +684,7 @@
         //--------------------------------------------------
         entries = new ArrayList<>();
         entries.add(new AppOpsManager.OpEntry(
-                ForceAppStandbyTracker.TARGET_OP,
+                AppStateTracker.TARGET_OP,
                 AppOpsManager.MODE_ALLOWED, 0, 0, 0, 0, null));
 
         ops.add(new PackageOps(PACKAGE_1, UID_10_1, entries));
@@ -644,7 +692,7 @@
         //--------------------------------------------------
         entries = new ArrayList<>();
         entries.add(new AppOpsManager.OpEntry(
-                ForceAppStandbyTracker.TARGET_OP,
+                AppStateTracker.TARGET_OP,
                 AppOpsManager.MODE_IGNORED, 0, 0, 0, 0, null));
         entries.add(new AppOpsManager.OpEntry(
                 AppOpsManager.OP_ACCESS_NOTIFICATIONS,
@@ -677,10 +725,10 @@
 
     @Test
     public void testPowerSaveListener() throws Exception {
-        final ForceAppStandbyTrackerTestable instance = newInstance();
+        final AppStateTrackerTestable instance = newInstance();
         callStart(instance);
 
-        ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class);
+        AppStateTracker.Listener l = mock(AppStateTracker.Listener.class);
         instance.addListener(l);
 
         // Power save on.
@@ -720,10 +768,10 @@
 
     @Test
     public void testAllListeners() throws Exception {
-        final ForceAppStandbyTrackerTestable instance = newInstance();
+        final AppStateTrackerTestable instance = newInstance();
         callStart(instance);
 
-        ForceAppStandbyTracker.Listener l = mock(ForceAppStandbyTracker.Listener.class);
+        AppStateTracker.Listener l = mock(AppStateTracker.Listener.class);
         instance.addListener(l);
 
         // -------------------------------------------------------------------------
@@ -1031,12 +1079,14 @@
 
     @Test
     public void testUserRemoved() throws Exception {
-        final ForceAppStandbyTrackerTestable instance = newInstance();
+        final AppStateTrackerTestable instance = newInstance();
         callStart(instance);
 
         mIUidObserver.onUidActive(UID_1);
         mIUidObserver.onUidActive(UID_10_1);
 
+        waitUntilMainHandlerDrain();
+
         setAppOps(UID_2, PACKAGE_2, true);
         setAppOps(UID_10_2, PACKAGE_2, true);
 
@@ -1064,7 +1114,7 @@
         // This is a small battery device
         mIsSmallBatteryDevice = true;
 
-        final ForceAppStandbyTrackerTestable instance = newInstance();
+        final AppStateTrackerTestable instance = newInstance();
         callStart(instance);
         assertFalse(instance.isForceAllAppsStandbyEnabled());
 
@@ -1090,7 +1140,7 @@
         // Not a small battery device, so plugged in status should not affect forced app standby
         mIsSmallBatteryDevice = false;
 
-        final ForceAppStandbyTrackerTestable instance = newInstance();
+        final AppStateTrackerTestable instance = newInstance();
         callStart(instance);
         assertFalse(instance.isForceAllAppsStandbyEnabled());
 
@@ -1139,7 +1189,7 @@
 
     private void checkAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray, boolean expected) {
         assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray),
-                expected, ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(prevArray, newArray));
+                expected, AppStateTracker.isAnyAppIdUnwhitelisted(prevArray, newArray));
 
         // Also test isAnyAppIdUnwhitelistedSlow.
         assertEquals("Input: " + Arrays.toString(prevArray) + " " + Arrays.toString(newArray),
@@ -1171,7 +1221,7 @@
             final int[] array2 = makeRandomArray();
 
             final boolean expected = isAnyAppIdUnwhitelistedSlow(array1, array2);
-            final boolean actual = ForceAppStandbyTracker.isAnyAppIdUnwhitelisted(array1, array2);
+            final boolean actual = AppStateTracker.isAnyAppIdUnwhitelisted(array1, array2);
 
             assertEquals("Input: " + Arrays.toString(array1) + " " + Arrays.toString(array2),
                     expected, actual);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index f9ffccd..0f0e3f3 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -255,6 +255,9 @@
         when(mKeyStoreProxy
                 .containsAlias("com.android.server.locksettings.recoverablekeystore/"
                         + "platform/42/1/decrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/encrypt")).thenReturn(true);
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
@@ -264,6 +267,56 @@
     }
 
     @Test
+    public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/encrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/decrypt")).thenReturn(true);
+
+        mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
+
+        verify(mKeyStoreProxy).containsAlias(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+        // Attempt to get regenerated key.
+        verify(mKeyStoreProxy).getKey(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                any());
+    }
+
+    @Test
+    public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/encrypt")).thenReturn(false); // was removed
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/decrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/encrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/decrypt")).thenReturn(true);
+
+        mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
+
+        verify(mKeyStoreProxy).containsAlias(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+        // Attempt to get regenerated key.
+        verify(mKeyStoreProxy).getKey(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+                any());
+    }
+
+    @Test
     public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
         doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
                 eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
@@ -274,7 +327,13 @@
                         + "platform/42/1/encrypt")).thenReturn(true);
         when(mKeyStoreProxy
                 .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/decrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
                         + "platform/42/2/encrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/decrypt")).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
@@ -295,11 +354,16 @@
 
         when(mKeyStoreProxy
                 .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/decrypt")).thenReturn(false); // was removed.
-
+                        + "platform/42/1/encrypt")).thenReturn(true);
         when(mKeyStoreProxy
                 .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/2/decrypt")).thenReturn(true); // new version is available
+                        + "platform/42/1/decrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/encrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/decrypt")).thenReturn(true);
 
         mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
 
@@ -312,14 +376,45 @@
     }
 
     @Test
-    public void getEncryptKey_generatesNewKeyIfOldWasRemoved() throws Exception {
+    public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception {
+        doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+                any());
         when(mKeyStoreProxy
                 .containsAlias("com.android.server.locksettings.recoverablekeystore/"
-                        + "platform/42/1/encrypt")).thenReturn(false); // was removed.
-
+                        + "platform/42/1/encrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
         when(mKeyStoreProxy
                 .containsAlias("com.android.server.locksettings.recoverablekeystore/"
                         + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/decrypt")).thenReturn(true);
+
+        mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
+
+        // Attempt to get regenerated key.
+        verify(mKeyStoreProxy).getKey(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                any());
+    }
+
+    @Test
+    public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/encrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/decrypt")).thenReturn(false); // was removed
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/decrypt")).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
@@ -332,10 +427,38 @@
     }
 
     @Test
-    public void getEncryptKey_getsEndryptKeyWithCorrectAlias() throws Exception {
+    public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/encrypt")).thenReturn(false); // was removed
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/decrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/encrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/2/decrypt")).thenReturn(true);
+
+        mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
+
+        verify(mKeyStoreProxy).containsAlias(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+        // Attempt to get regenerated key.
+        verify(mKeyStoreProxy).getKey(
+                eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+                any());
+    }
+
+    @Test
+    public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception {
         when(mKeyStoreProxy
                 .containsAlias("com.android.server.locksettings.recoverablekeystore/"
                         + "platform/42/1/encrypt")).thenReturn(true);
+        when(mKeyStoreProxy
+                .containsAlias("com.android.server.locksettings.recoverablekeystore/"
+                        + "platform/42/1/decrypt")).thenReturn(true);
 
         mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
 
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 473a813..2343dee 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -125,6 +125,7 @@
     private static final byte[] RECOVERY_RESPONSE_HEADER =
             "V1 reencrypted_recovery_key".getBytes(StandardCharsets.UTF_8);
     private static final String TEST_ALIAS = "nick";
+    private static final String TEST_ALIAS2 = "bob";
     private static final int RECOVERABLE_KEY_SIZE_BYTES = 32;
     private static final int GENERATION_ID = 1;
     private static final byte[] NONCE = getUtf8Bytes("nonce");
@@ -424,7 +425,7 @@
     }
 
     @Test
-    public void recoverKeys_throwsIfFailedToDecryptAnApplicationKey() throws Exception {
+    public void recoverKeys_throwsIfFailedToDecryptAllApplicationKeys() throws Exception {
         mRecoverableKeyStoreManager.startRecoverySession(
                 TEST_SESSION_ID,
                 TEST_PUBLIC_KEY,
@@ -442,7 +443,7 @@
                 keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
         WrappedApplicationKey badApplicationKey = new WrappedApplicationKey(
                 TEST_ALIAS,
-                randomBytes(32));
+                encryptedApplicationKey(randomRecoveryKey(), randomBytes(32)));
 
         try {
             mRecoverableKeyStoreManager.recoverKeys(
@@ -451,7 +452,7 @@
                     /*applicationKeys=*/ ImmutableList.of(badApplicationKey));
             fail("should have thrown");
         } catch (ServiceSpecificException e) {
-            assertThat(e.getMessage()).startsWith("Failed to recover key with alias 'nick'");
+            assertThat(e.getMessage()).startsWith("Failed to recover any of the application keys");
         }
     }
 
@@ -487,6 +488,44 @@
     }
 
     @Test
+    public void recoverKeys_worksOnOtherApplicationKeysIfOneDecryptionFails() throws Exception {
+        mRecoverableKeyStoreManager.startRecoverySession(
+                TEST_SESSION_ID,
+                TEST_PUBLIC_KEY,
+                TEST_VAULT_PARAMS,
+                TEST_VAULT_CHALLENGE,
+                ImmutableList.of(new KeyChainProtectionParams(
+                        TYPE_LOCKSCREEN,
+                        UI_FORMAT_PASSWORD,
+                        KeyDerivationParams.createSha256Params(TEST_SALT),
+                        TEST_SECRET)));
+        byte[] keyClaimant = mRecoverySessionStorage.get(Binder.getCallingUid(), TEST_SESSION_ID)
+                .getKeyClaimant();
+        SecretKey recoveryKey = randomRecoveryKey();
+        byte[] encryptedClaimResponse = encryptClaimResponse(
+                keyClaimant, TEST_SECRET, TEST_VAULT_PARAMS, recoveryKey);
+
+        byte[] applicationKeyBytes1 = randomBytes(32);
+        byte[] applicationKeyBytes2 = randomBytes(32);
+
+        WrappedApplicationKey applicationKey1 = new WrappedApplicationKey(
+                TEST_ALIAS,
+                // Use a different recovery key here, so the decryption will fail
+                encryptedApplicationKey(randomRecoveryKey(), applicationKeyBytes1));
+        WrappedApplicationKey applicationKey2 = new WrappedApplicationKey(
+                TEST_ALIAS2,
+                encryptedApplicationKey(recoveryKey, applicationKeyBytes2));
+
+        Map<String, byte[]> recoveredKeys = mRecoverableKeyStoreManager.recoverKeys(
+                TEST_SESSION_ID,
+                encryptedClaimResponse,
+                ImmutableList.of(applicationKey1, applicationKey2));
+
+        assertThat(recoveredKeys).hasSize(1);
+        assertThat(recoveredKeys.get(TEST_ALIAS2)).isEqualTo(applicationKeyBytes2);
+    }
+
+    @Test
     public void setSnapshotCreatedPendingIntent() throws Exception {
         int uid = Binder.getCallingUid();
         PendingIntent intent = PendingIntent.getBroadcast(
diff --git a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
index 5de393c..a628b7b 100644
--- a/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
+++ b/services/tests/servicestests/src/com/android/server/policy/FakeWindowState.java
@@ -79,6 +79,11 @@
     }
 
     @Override
+    public Point getShownPositionLw() {
+        return new Point(parentFrame.left, parentFrame.top);
+    }
+
+    @Override
     public Rect getDisplayFrameLw() {
         return displayFrame;
     }