Merge "Add confirmation UI protocol to Keystore AIDL definition"
diff --git a/Android.mk b/Android.mk
index 2254008..32e4bfa 100644
--- a/Android.mk
+++ b/Android.mk
@@ -823,6 +823,28 @@
     $(call all-proto-files-under, libs/incident/proto/android/os)
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
+# ==== hiddenapi lists =======================================
+
+# Generate light greylist as private API minus (blacklist plus dark greylist).
+
+$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): PRIVATE_API := $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE)
+$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): BLACKLIST := $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST)
+$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): DARK_GREYLIST := $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)
+$(INTERNAL_PLATFORM_HIDDENAPI_LIGHT_GREYLIST): $(INTERNAL_PLATFORM_PRIVATE_DEX_API_FILE) \
+                                               $(INTERNAL_PLATFORM_HIDDENAPI_BLACKLIST) \
+                                               $(INTERNAL_PLATFORM_HIDDENAPI_DARK_GREYLIST)
+	if [ ! -z "`comm -12 <(sort $(BLACKLIST)) <(sort $(DARK_GREYLIST))`" ]; then \
+		echo "There should be no overlap between $(BLACKLIST) and $(DARK_GREYLIST)" 1>&2; \
+		exit 1; \
+	elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(BLACKLIST))`" ]; then \
+		echo "$(BLACKLIST) must be a subset of $(PRIVATE_API)" 1>&2; \
+		exit 2; \
+	elif [ ! -z "`comm -13 <(sort $(PRIVATE_API)) <(sort $(DARK_GREYLIST))`" ]; then \
+		echo "$(DARK_GREYLIST) must be a subset of $(PRIVATE_API)" 1>&2; \
+		exit 3; \
+	fi
+	comm -23 <(sort $(PRIVATE_API)) <(sort $(BLACKLIST) $(DARK_GREYLIST)) > $@
+
 # Include subdirectory makefiles
 # ============================================================
 
diff --git a/api/current.txt b/api/current.txt
index 61465ac..bbd9e18 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6,6 +6,7 @@
 
   public static final class Manifest.permission {
     ctor public Manifest.permission();
+    field public static final java.lang.String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER";
     field public static final java.lang.String ACCESS_CHECKIN_PROPERTIES = "android.permission.ACCESS_CHECKIN_PROPERTIES";
     field public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
     field public static final java.lang.String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
@@ -1810,6 +1811,7 @@
   public static final class R.id {
     ctor public R.id();
     field public static final int accessibilityActionContextClick = 16908348; // 0x102003c
+    field public static final int accessibilityActionHideTooltip = 16908357; // 0x1020045
     field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042
     field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a
     field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039
@@ -1818,6 +1820,7 @@
     field public static final int accessibilityActionScrollUp = 16908344; // 0x1020038
     field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d
     field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036
+    field public static final int accessibilityActionShowTooltip = 16908356; // 0x1020044
     field public static final int addToDictionary = 16908330; // 0x102002a
     field public static final int autofill = 16908355; // 0x1020043
     field public static final int background = 16908288; // 0x1020000
@@ -5224,6 +5227,7 @@
     field public static final java.lang.String EXTRA_PROGRESS = "android.progress";
     field public static final java.lang.String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
     field public static final java.lang.String EXTRA_PROGRESS_MAX = "android.progressMax";
+    field public static final java.lang.String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
     field public static final java.lang.String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
     field public static final deprecated java.lang.String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
     field public static final java.lang.String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
@@ -6890,6 +6894,8 @@
     method public void onRestore(android.app.backup.BackupDataInput, long, android.os.ParcelFileDescriptor) throws java.io.IOException;
     method public void onRestoreFile(android.os.ParcelFileDescriptor, long, java.io.File, int, long, long) throws java.io.IOException;
     method public void onRestoreFinished();
+    field public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1; // 0x1
+    field public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2; // 0x2
     field public static final int TYPE_DIRECTORY = 2; // 0x2
     field public static final int TYPE_FILE = 1; // 0x1
   }
@@ -6917,6 +6923,7 @@
 
   public class BackupDataOutput {
     method public long getQuota();
+    method public int getTransportFlags();
     method public int writeEntityData(byte[], int) throws java.io.IOException;
     method public int writeEntityHeader(java.lang.String, int) throws java.io.IOException;
   }
@@ -6931,7 +6938,7 @@
     ctor public BackupManager(android.content.Context);
     method public void dataChanged();
     method public static void dataChanged(java.lang.String);
-    method public int requestRestore(android.app.backup.RestoreObserver);
+    method public deprecated int requestRestore(android.app.backup.RestoreObserver);
   }
 
   public class FileBackupHelper implements android.app.backup.BackupHelper {
@@ -6942,6 +6949,7 @@
 
   public class FullBackupDataOutput {
     method public long getQuota();
+    method public int getTransportFlags();
   }
 
   public abstract class RestoreObserver {
@@ -11138,6 +11146,7 @@
     field public static final java.lang.String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
     field public static final java.lang.String FEATURE_CAMERA = "android.hardware.camera";
     field public static final java.lang.String FEATURE_CAMERA_ANY = "android.hardware.camera.any";
+    field public static final java.lang.String FEATURE_CAMERA_AR = "android.hardware.camera.ar";
     field public static final java.lang.String FEATURE_CAMERA_AUTOFOCUS = "android.hardware.camera.autofocus";
     field public static final java.lang.String FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING = "android.hardware.camera.capability.manual_post_processing";
     field public static final java.lang.String FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR = "android.hardware.camera.capability.manual_sensor";
@@ -11197,6 +11206,7 @@
     field public static final java.lang.String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector";
     field public static final java.lang.String FEATURE_SIP = "android.software.sip";
     field public static final java.lang.String FEATURE_SIP_VOIP = "android.software.sip.voip";
+    field public static final java.lang.String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore";
     field public static final java.lang.String FEATURE_TELEPHONY = "android.hardware.telephony";
     field public static final java.lang.String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
     field public static final java.lang.String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
@@ -11551,15 +11561,15 @@
 
   public final class AssetManager implements java.lang.AutoCloseable {
     method public void close();
-    method public final java.lang.String[] getLocales();
-    method public final java.lang.String[] list(java.lang.String) throws java.io.IOException;
-    method public final java.io.InputStream open(java.lang.String) throws java.io.IOException;
-    method public final java.io.InputStream open(java.lang.String, int) throws java.io.IOException;
-    method public final android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException;
-    method public final android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException;
-    method public final android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException;
-    method public final android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException;
-    method public final android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException;
+    method public java.lang.String[] getLocales();
+    method public java.lang.String[] list(java.lang.String) throws java.io.IOException;
+    method public java.io.InputStream open(java.lang.String) throws java.io.IOException;
+    method public java.io.InputStream open(java.lang.String, int) throws java.io.IOException;
+    method public android.content.res.AssetFileDescriptor openFd(java.lang.String) throws java.io.IOException;
+    method public android.content.res.AssetFileDescriptor openNonAssetFd(java.lang.String) throws java.io.IOException;
+    method public android.content.res.AssetFileDescriptor openNonAssetFd(int, java.lang.String) throws java.io.IOException;
+    method public android.content.res.XmlResourceParser openXmlResourceParser(java.lang.String) throws java.io.IOException;
+    method public android.content.res.XmlResourceParser openXmlResourceParser(int, java.lang.String) throws java.io.IOException;
     field public static final int ACCESS_BUFFER = 3; // 0x3
     field public static final int ACCESS_RANDOM = 1; // 0x1
     field public static final int ACCESS_STREAMING = 2; // 0x2
@@ -16221,6 +16231,7 @@
 
   public final class TotalCaptureResult extends android.hardware.camera2.CaptureResult {
     method public java.util.List<android.hardware.camera2.CaptureResult> getPartialResults();
+    method public <T> T getPhysicalCameraKey(android.hardware.camera2.CaptureResult.Key<T>, java.lang.String);
   }
 
 }
@@ -23115,6 +23126,7 @@
 
   public static final class MediaCodecInfo.EncoderCapabilities {
     method public android.util.Range<java.lang.Integer> getComplexityRange();
+    method public android.util.Range<java.lang.Integer> getQualityRange();
     method public boolean isBitrateModeSupported(int);
     field public static final int BITRATE_MODE_CBR = 2; // 0x2
     field public static final int BITRATE_MODE_CQ = 0; // 0x0
@@ -23457,6 +23469,7 @@
     field public static final java.lang.String KEY_PRIORITY = "priority";
     field public static final java.lang.String KEY_PROFILE = "profile";
     field public static final java.lang.String KEY_PUSH_BLANK_BUFFERS_ON_STOP = "push-blank-buffers-on-shutdown";
+    field public static final java.lang.String KEY_QUALITY = "quality";
     field public static final java.lang.String KEY_REPEAT_PREVIOUS_FRAME_AFTER = "repeat-previous-frame-after";
     field public static final java.lang.String KEY_ROTATION = "rotation-degrees";
     field public static final java.lang.String KEY_SAMPLE_RATE = "sample-rate";
@@ -27586,14 +27599,14 @@
     field public java.lang.String providerFriendlyName;
     field public long[] roamingConsortiumIds;
     field public int status;
-    field public java.lang.String[] wepKeys;
-    field public int wepTxKeyIndex;
+    field public deprecated java.lang.String[] wepKeys;
+    field public deprecated int wepTxKeyIndex;
   }
 
   public static class WifiConfiguration.AuthAlgorithm {
     field public static final int LEAP = 2; // 0x2
     field public static final int OPEN = 0; // 0x0
-    field public static final int SHARED = 1; // 0x1
+    field public static final deprecated int SHARED = 1; // 0x1
     field public static final java.lang.String[] strings;
     field public static final java.lang.String varName = "auth_alg";
   }
@@ -27601,8 +27614,8 @@
   public static class WifiConfiguration.GroupCipher {
     field public static final int CCMP = 3; // 0x3
     field public static final int TKIP = 2; // 0x2
-    field public static final int WEP104 = 1; // 0x1
-    field public static final int WEP40 = 0; // 0x0
+    field public static final deprecated int WEP104 = 1; // 0x1
+    field public static final deprecated int WEP40 = 0; // 0x0
     field public static final java.lang.String[] strings;
     field public static final java.lang.String varName = "group";
   }
@@ -27611,7 +27624,7 @@
     field public static final int IEEE8021X = 3; // 0x3
     field public static final int NONE = 0; // 0x0
     field public static final int WPA_EAP = 2; // 0x2
-    field public static final int WPA_PSK = 1; // 0x1
+    field public static final deprecated int WPA_PSK = 1; // 0x1
     field public static final java.lang.String[] strings;
     field public static final java.lang.String varName = "key_mgmt";
   }
@@ -27619,14 +27632,14 @@
   public static class WifiConfiguration.PairwiseCipher {
     field public static final int CCMP = 2; // 0x2
     field public static final int NONE = 0; // 0x0
-    field public static final int TKIP = 1; // 0x1
+    field public static final deprecated int TKIP = 1; // 0x1
     field public static final java.lang.String[] strings;
     field public static final java.lang.String varName = "pairwise";
   }
 
   public static class WifiConfiguration.Protocol {
     field public static final int RSN = 1; // 0x1
-    field public static final int WPA = 0; // 0x0
+    field public static final deprecated int WPA = 0; // 0x0
     field public static final java.lang.String[] strings;
     field public static final java.lang.String varName = "proto";
   }
@@ -38097,6 +38110,7 @@
     method public boolean isInvalidatedByBiometricEnrollment();
     method public boolean isRandomizedEncryptionRequired();
     method public boolean isStrongBoxBacked();
+    method public boolean isTrustedUserPresenceRequired();
     method public boolean isUserAuthenticationRequired();
     method public boolean isUserAuthenticationValidWhileOnBody();
   }
@@ -38122,6 +38136,7 @@
     method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date);
     method public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean);
     method public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...);
+    method public android.security.keystore.KeyGenParameterSpec.Builder setTrustedUserPresenceRequired(boolean);
     method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationRequired(boolean);
     method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidWhileOnBody(boolean);
     method public android.security.keystore.KeyGenParameterSpec.Builder setUserAuthenticationValidityDurationSeconds(int);
@@ -38142,6 +38157,7 @@
     method public int getUserAuthenticationValidityDurationSeconds();
     method public boolean isInsideSecureHardware();
     method public boolean isInvalidatedByBiometricEnrollment();
+    method public boolean isTrustedUserPresenceRequired();
     method public boolean isUserAuthenticationRequired();
     method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
     method public boolean isUserAuthenticationValidWhileOnBody();
@@ -38233,7 +38249,6 @@
   }
 
   public class StrongBoxUnavailableException extends java.security.ProviderException {
-    ctor public StrongBoxUnavailableException();
   }
 
   public class UserNotAuthenticatedException extends java.security.InvalidKeyException {
@@ -38242,6 +38257,12 @@
     ctor public UserNotAuthenticatedException(java.lang.String, java.lang.Throwable);
   }
 
+  public class UserPresenceUnavailableException extends java.security.InvalidAlgorithmParameterException {
+    ctor public UserPresenceUnavailableException();
+    ctor public UserPresenceUnavailableException(java.lang.String);
+    ctor public UserPresenceUnavailableException(java.lang.String, java.lang.Throwable);
+  }
+
   public class WrappedKeyEntry implements java.security.KeyStore.Entry {
     ctor public WrappedKeyEntry(byte[], java.lang.String, java.lang.String, java.security.spec.AlgorithmParameterSpec);
     method public java.security.spec.AlgorithmParameterSpec getAlgorithmParameterSpec();
@@ -39479,11 +39500,6 @@
     field public final int errno;
   }
 
-  public class Int32Ref {
-    ctor public Int32Ref(int);
-    field public int value;
-  }
-
   public class Int64Ref {
     ctor public Int64Ref(long);
     field public long value;
@@ -39583,7 +39599,6 @@
     method public static int umask(int);
     method public static android.system.StructUtsname uname();
     method public static void unsetenv(java.lang.String) throws android.system.ErrnoException;
-    method public static int waitpid(int, android.system.Int32Ref, int) throws android.system.ErrnoException;
     method public static int write(java.io.FileDescriptor, java.nio.ByteBuffer) throws android.system.ErrnoException, java.io.InterruptedIOException;
     method public static int write(java.io.FileDescriptor, byte[], int, int) throws android.system.ErrnoException, java.io.InterruptedIOException;
     method public static int writev(java.io.FileDescriptor, java.lang.Object[], int[], int[]) throws android.system.ErrnoException, java.io.InterruptedIOException;
@@ -40408,6 +40423,7 @@
     method public void onCallEvent(java.lang.String, android.os.Bundle);
     method public void onDisconnect();
     method public void onExtrasChanged(android.os.Bundle);
+    method public void onHandoverComplete();
     method public void onHold();
     method public void onPlayDtmfTone(char);
     method public void onPostDialContinue(boolean);
@@ -41101,6 +41117,8 @@
     method public void notifyConfigChangedForSubId(int);
     field public static final java.lang.String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
+    field public static final java.lang.String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
+    field public static final java.lang.String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
     field public static final java.lang.String KEY_ADDITIONAL_CALL_SETTING_BOOL = "additional_call_setting_bool";
     field public static final java.lang.String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
     field public static final java.lang.String KEY_ALLOW_ADD_CALL_DURING_VIDEO_CALL_BOOL = "allow_add_call_during_video_call";
@@ -41283,6 +41301,7 @@
   }
 
   public final class CellIdentityLte extends android.telephony.CellIdentity {
+    method public int getBandwidth();
     method public int getCi();
     method public int getEarfcn();
     method public deprecated int getMcc();
@@ -41326,8 +41345,13 @@
 
   public abstract class CellInfo implements android.os.Parcelable {
     method public int describeContents();
+    method public int getCellConnectionStatus();
     method public long getTimeStamp();
     method public boolean isRegistered();
+    field public static final int CONNECTION_NONE = 0; // 0x0
+    field public static final int CONNECTION_PRIMARY_SERVING = 1; // 0x1
+    field public static final int CONNECTION_SECONDARY_SERVING = 2; // 0x2
+    field public static final int CONNECTION_UNKNOWN = 2147483647; // 0x7fffffff
     field public static final android.os.Parcelable.Creator<android.telephony.CellInfo> CREATOR;
   }
 
@@ -41640,6 +41664,9 @@
     ctor public ServiceState(android.os.Parcel);
     method protected void copyFrom(android.telephony.ServiceState);
     method public int describeContents();
+    method public int[] getCellBandwidths();
+    method public int getChannelNumber();
+    method public int getDuplexMode();
     method public boolean getIsManualSelection();
     method public int getNetworkId();
     method public java.lang.String getOperatorAlphaLong();
@@ -41656,6 +41683,9 @@
     method public void setStateOutOfService();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<android.telephony.ServiceState> CREATOR;
+    field public static final int DUPLEX_MODE_FDD = 1; // 0x1
+    field public static final int DUPLEX_MODE_TDD = 2; // 0x2
+    field public static final int DUPLEX_MODE_UNKNOWN = 0; // 0x0
     field public static final int STATE_EMERGENCY_ONLY = 2; // 0x2
     field public static final int STATE_IN_SERVICE = 0; // 0x0
     field public static final int STATE_OUT_OF_SERVICE = 1; // 0x1
@@ -48688,6 +48718,7 @@
     method public java.lang.CharSequence getText();
     method public int getTextSelectionEnd();
     method public int getTextSelectionStart();
+    method public java.lang.CharSequence getTooltipText();
     method public android.view.accessibility.AccessibilityNodeInfo getTraversalAfter();
     method public android.view.accessibility.AccessibilityNodeInfo getTraversalBefore();
     method public java.lang.String getViewIdResourceName();
@@ -48775,6 +48806,7 @@
     method public void setSource(android.view.View, int);
     method public void setText(java.lang.CharSequence);
     method public void setTextSelection(int, int);
+    method public void setTooltipText(java.lang.CharSequence);
     method public void setTraversalAfter(android.view.View);
     method public void setTraversalAfter(android.view.View, int);
     method public void setTraversalBefore(android.view.View);
@@ -48844,6 +48876,7 @@
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DISMISS;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS;
+    field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_HIDE_TOOLTIP;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_LONG_CLICK;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_MOVE_WINDOW;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_NEXT_AT_MOVEMENT_GRANULARITY;
@@ -48863,6 +48896,7 @@
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_SELECTION;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_TEXT;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_ON_SCREEN;
+    field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_TOOLTIP;
   }
 
   public static final class AccessibilityNodeInfo.CollectionInfo {
@@ -49581,6 +49615,7 @@
     method public abstract boolean performEditorAction(int);
     method public abstract boolean performPrivateCommand(java.lang.String, android.os.Bundle);
     method public abstract boolean reportFullscreenMode(boolean);
+    method public default void reportLanguageHint(android.os.LocaleList);
     method public abstract boolean requestCursorUpdates(int);
     method public abstract boolean sendKeyEvent(android.view.KeyEvent);
     method public abstract boolean setComposingRegion(int, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 58d06b3..fd25e1b 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -253,6 +253,7 @@
   public class AppOpsManager {
     method public static java.lang.String[] getOpStrs();
     method public void setUidMode(java.lang.String, int, int);
+    field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
     field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
     field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
     field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
@@ -354,6 +355,19 @@
     method public org.json.JSONObject toJson() throws org.json.JSONException;
   }
 
+  public final class StatsManager {
+    method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String);
+    method public byte[] getData(long);
+    method public byte[] getMetadata();
+    method public boolean removeConfiguration(long);
+    method public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent);
+    field public static final java.lang.String EXTRA_STATS_CONFIG_KEY = "android.app.extra.STATS_CONFIG_KEY";
+    field public static final java.lang.String EXTRA_STATS_CONFIG_UID = "android.app.extra.STATS_CONFIG_UID";
+    field public static final java.lang.String EXTRA_STATS_DIMENSIONS_VALUE = "android.app.extra.STATS_DIMENSIONS_VALUE";
+    field public static final java.lang.String EXTRA_STATS_SUBSCRIPTION_ID = "android.app.extra.STATS_SUBSCRIPTION_ID";
+    field public static final java.lang.String EXTRA_STATS_SUBSCRIPTION_RULE_ID = "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
+  }
+
   public class VrManager {
     method public void setAndBindVrCompositor(android.content.ComponentName);
     method public void setPersistentVrModeEnabled(boolean);
@@ -436,7 +450,7 @@
     method public java.lang.String[] listAllTransports();
     method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver);
     method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver, android.app.backup.BackupManagerMonitor, int);
-    method public int requestRestore(android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor);
+    method public deprecated int requestRestore(android.app.backup.RestoreObserver, android.app.backup.BackupManagerMonitor);
     method public deprecated java.lang.String selectBackupTransport(java.lang.String);
     method public void selectBackupTransport(android.content.ComponentName, android.app.backup.SelectBackupTransportCallback);
     method public void setAutoRestore(boolean);
@@ -513,6 +527,7 @@
     field public static final int LOG_EVENT_ID_SIGNATURE_MISMATCH = 29; // 0x1d
     field public static final int LOG_EVENT_ID_SYSTEM_APP_NO_AGENT = 38; // 0x26
     field public static final int LOG_EVENT_ID_TRANSPORT_IS_NULL = 50; // 0x32
+    field public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51; // 0x33
     field public static final int LOG_EVENT_ID_UNKNOWN_VERSION = 44; // 0x2c
     field public static final int LOG_EVENT_ID_VERSIONS_MATCH = 35; // 0x23
     field public static final int LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER = 36; // 0x24
@@ -554,6 +569,7 @@
     method public long getCurrentRestoreSet();
     method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor);
     method public int getRestoreData(android.os.ParcelFileDescriptor);
+    method public int getTransportFlags();
     method public int initializeDevice();
     method public boolean isAppEligibleForBackup(android.content.pm.PackageInfo, boolean);
     method public java.lang.String name();
@@ -569,9 +585,12 @@
     method public java.lang.String transportDirName();
     field public static final int AGENT_ERROR = -1003; // 0xfffffc15
     field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14
+    field public static final int FLAG_INCREMENTAL = 2; // 0x2
+    field public static final int FLAG_NON_INCREMENTAL = 4; // 0x4
     field public static final int FLAG_USER_INITIATED = 1; // 0x1
     field public static final int NO_MORE_DATA = -1; // 0xffffffff
     field public static final int TRANSPORT_ERROR = -1000; // 0xfffffc18
+    field public static final int TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = -1006; // 0xfffffc12
     field public static final int TRANSPORT_NOT_INITIALIZED = -1001; // 0xfffffc17
     field public static final int TRANSPORT_OK = 0; // 0x0
     field public static final int TRANSPORT_PACKAGE_REJECTED = -1002; // 0xfffffc16
@@ -2907,9 +2926,23 @@
     method public java.lang.String getInterfaceName();
   }
 
+  public final class IpSecTransform implements java.lang.AutoCloseable {
+    method public void startNattKeepalive(android.net.IpSecTransform.NattKeepaliveCallback, int, android.os.Handler) throws java.io.IOException;
+    method public void stopNattKeepalive();
+  }
+
   public static class IpSecTransform.Builder {
     method public android.net.IpSecTransform buildTunnelModeTransform(java.net.InetAddress, android.net.IpSecManager.SecurityParameterIndex) throws java.io.IOException, android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    method public android.net.IpSecTransform.Builder setNattKeepalive(int);
+  }
+
+  public static class IpSecTransform.NattKeepaliveCallback {
+    ctor public IpSecTransform.NattKeepaliveCallback();
+    method public void onError(int);
+    method public void onStarted();
+    method public void onStopped();
+    field public static final int ERROR_HARDWARE_ERROR = 3; // 0x3
+    field public static final int ERROR_HARDWARE_UNSUPPORTED = 2; // 0x2
+    field public static final int ERROR_INVALID_NETWORK = 1; // 0x1
   }
 
   public class NetworkKey implements android.os.Parcelable {
@@ -3656,6 +3689,27 @@
     method public abstract void onResult(android.os.Bundle);
   }
 
+  public final class StatsDimensionsValue implements android.os.Parcelable {
+    method public int describeContents();
+    method public boolean getBooleanValue();
+    method public int getField();
+    method public float getFloatValue();
+    method public int getIntValue();
+    method public long getLongValue();
+    method public java.lang.String getStringValue();
+    method public java.util.List<android.os.StatsDimensionsValue> getTupleValueList();
+    method public int getValueType();
+    method public boolean isValueType(int);
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int BOOLEAN_VALUE_TYPE = 5; // 0x5
+    field public static final android.os.Parcelable.Creator<android.os.StatsDimensionsValue> CREATOR;
+    field public static final int FLOAT_VALUE_TYPE = 6; // 0x6
+    field public static final int INT_VALUE_TYPE = 3; // 0x3
+    field public static final int LONG_VALUE_TYPE = 4; // 0x4
+    field public static final int STRING_VALUE_TYPE = 2; // 0x2
+    field public static final int TUPLE_VALUE_TYPE = 7; // 0x7
+  }
+
   public class SystemProperties {
     method public static java.lang.String get(java.lang.String);
     method public static java.lang.String get(java.lang.String, java.lang.String);
@@ -4735,6 +4789,24 @@
   public final class SmsManager {
     method public void sendMultipartTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
     method public void sendTextMessageWithoutPersisting(java.lang.String, java.lang.String, java.lang.String, android.app.PendingIntent, android.app.PendingIntent);
+    field public static final int RESULT_CANCELLED = 23; // 0x17
+    field public static final int RESULT_ENCODING_ERROR = 18; // 0x12
+    field public static final int RESULT_ERROR_FDN_CHECK_FAILURE = 6; // 0x6
+    field public static final int RESULT_ERROR_NONE = 0; // 0x0
+    field public static final int RESULT_INTERNAL_ERROR = 21; // 0x15
+    field public static final int RESULT_INVALID_ARGUMENTS = 11; // 0xb
+    field public static final int RESULT_INVALID_SMSC_ADDRESS = 19; // 0x13
+    field public static final int RESULT_INVALID_SMS_FORMAT = 14; // 0xe
+    field public static final int RESULT_INVALID_STATE = 12; // 0xc
+    field public static final int RESULT_MODEM_ERROR = 16; // 0x10
+    field public static final int RESULT_NETWORK_ERROR = 17; // 0x11
+    field public static final int RESULT_NETWORK_REJECT = 10; // 0xa
+    field public static final int RESULT_NO_MEMORY = 13; // 0xd
+    field public static final int RESULT_NO_RESOURCES = 22; // 0x16
+    field public static final int RESULT_OPERATION_NOT_ALLOWED = 20; // 0x14
+    field public static final int RESULT_RADIO_NOT_AVAILABLE = 9; // 0x9
+    field public static final int RESULT_REQUEST_NOT_SUPPORTED = 24; // 0x18
+    field public static final int RESULT_SYSTEM_ERROR = 15; // 0xf
   }
 
   public class SubscriptionManager {
@@ -4995,6 +5067,30 @@
 
 }
 
+package android.telephony.ims.internal.stub {
+
+  public class SmsImplBase {
+    ctor public SmsImplBase();
+    method public void acknowledgeSms(int, int, int);
+    method public void acknowledgeSmsReport(int, int, int);
+    method public java.lang.String getSmsFormat();
+    method public void onReady();
+    method public final void onSendSmsResult(int, int, int, int) throws java.lang.RuntimeException;
+    method public final void onSmsReceived(int, java.lang.String, byte[]) throws java.lang.RuntimeException;
+    method public final void onSmsStatusReportReceived(int, int, java.lang.String, byte[]) throws java.lang.RuntimeException;
+    method public void sendSms(int, int, java.lang.String, java.lang.String, boolean, byte[]);
+    field public static final int DELIVER_STATUS_ERROR = 2; // 0x2
+    field public static final int DELIVER_STATUS_OK = 1; // 0x1
+    field public static final int SEND_STATUS_ERROR = 2; // 0x2
+    field public static final int SEND_STATUS_ERROR_FALLBACK = 4; // 0x4
+    field public static final int SEND_STATUS_ERROR_RETRY = 3; // 0x3
+    field public static final int SEND_STATUS_OK = 1; // 0x1
+    field public static final int STATUS_REPORT_STATUS_ERROR = 2; // 0x2
+    field public static final int STATUS_REPORT_STATUS_OK = 1; // 0x1
+  }
+
+}
+
 package android.telephony.mbms {
 
   public final class DownloadRequest implements android.os.Parcelable {
@@ -5096,16 +5192,6 @@
     method public int getUid();
   }
 
-  public final class StatsManager {
-    method public boolean addConfiguration(java.lang.String, byte[], java.lang.String, java.lang.String);
-    method public boolean addConfiguration(long, byte[], java.lang.String, java.lang.String);
-    method public byte[] getData(java.lang.String);
-    method public byte[] getData(long);
-    method public byte[] getMetadata();
-    method public boolean removeConfiguration(java.lang.String);
-    method public boolean removeConfiguration(long);
-  }
-
 }
 
 package android.view {
diff --git a/api/test-current.txt b/api/test-current.txt
index 254fc15..4e8f904 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -48,6 +48,7 @@
   public class AppOpsManager {
     method public static java.lang.String[] getOpStrs();
     method public void setMode(int, int, java.lang.String, int);
+    field public static final java.lang.String OPSTR_ACCEPT_HANDOVER = "android:accept_handover";
     field public static final java.lang.String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
     field public static final java.lang.String OPSTR_ACTIVATE_VPN = "android:activate_vpn";
     field public static final java.lang.String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
@@ -1036,6 +1037,10 @@
 
 package android.widget {
 
+  public abstract class AbsListView extends android.widget.AdapterView implements android.widget.Filter.FilterListener android.text.TextWatcher android.view.ViewTreeObserver.OnGlobalLayoutListener android.view.ViewTreeObserver.OnTouchModeChangeListener {
+    method public final boolean shouldDrawSelector();
+  }
+
   public class CalendarView extends android.widget.FrameLayout {
     method public boolean getBoundsForDate(long, android.graphics.Rect);
   }
diff --git a/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index a7daa3f..eabbb96 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -62,6 +62,7 @@
     src/storage/StorageManager.cpp \
     src/StatsLogProcessor.cpp \
     src/StatsService.cpp \
+    src/subscriber/SubscriberReporter.cpp \
     src/HashableDimensionKey.cpp \
     src/guardrail/MemoryLeakTrackUtil.cpp \
     src/guardrail/StatsdStats.cpp
@@ -221,6 +222,44 @@
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
+##############################
+# statsd micro benchmark
+##############################
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := statsd_benchmark
+
+LOCAL_SRC_FILES := $(statsd_common_src) \
+                   benchmark/main.cpp \
+                   benchmark/hello_world_benchmark.cpp \
+                   benchmark/log_event_benchmark.cpp
+
+LOCAL_C_INCLUDES := $(statsd_common_c_includes)
+
+LOCAL_CFLAGS := -Wall \
+                -Werror \
+                -Wno-unused-parameter \
+                -Wno-unused-variable \
+                -Wno-unused-function \
+
+# Bug: http://b/29823425 Disable -Wvarargs for Clang update to r271374
+LOCAL_CFLAGS += -Wno-varargs
+
+LOCAL_AIDL_INCLUDES := $(statsd_common_aidl_includes)
+
+LOCAL_STATIC_LIBRARIES := \
+    $(statsd_common_static_libraries)
+
+LOCAL_SHARED_LIBRARIES := $(statsd_common_shared_libraries) \
+    libgtest_prod
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite
+
+LOCAL_MODULE_TAGS := eng tests
+
+include $(BUILD_NATIVE_BENCHMARK)
+
+
 statsd_common_src:=
 statsd_common_aidl_includes:=
 statsd_common_c_includes:=
@@ -228,6 +267,4 @@
 statsd_common_shared_libraries:=
 
 
-##############################
-
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/cmds/statsd/benchmark/hello_world_benchmark.cpp b/cmds/statsd/benchmark/hello_world_benchmark.cpp
new file mode 100644
index 0000000..c732d39
--- /dev/null
+++ b/cmds/statsd/benchmark/hello_world_benchmark.cpp
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+#include "benchmark/benchmark.h"
+
+static void BM_StringCreation(benchmark::State& state) {
+    while (state.KeepRunning()) std::string empty_string;
+}
+// Register the function as a benchmark
+BENCHMARK(BM_StringCreation);
+
+// Define another benchmark
+static void BM_StringCopy(benchmark::State& state) {
+    std::string x = "hello";
+    while (state.KeepRunning()) std::string copy(x);
+}
+BENCHMARK(BM_StringCopy);
diff --git a/cmds/statsd/benchmark/log_event_benchmark.cpp b/cmds/statsd/benchmark/log_event_benchmark.cpp
new file mode 100644
index 0000000..43addc2
--- /dev/null
+++ b/cmds/statsd/benchmark/log_event_benchmark.cpp
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+#include <vector>
+#include "benchmark/benchmark.h"
+#include "logd/LogEvent.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using std::vector;
+
+/* Special markers for android_log_list_element type */
+static const char EVENT_TYPE_LIST_STOP = '\n'; /* declare end of list  */
+static const char EVENT_TYPE_UNKNOWN = '?';    /* protocol error       */
+
+static const char EVENT_TYPE_INT = 0;
+static const char EVENT_TYPE_LONG = 1;
+static const char EVENT_TYPE_STRING = 2;
+static const char EVENT_TYPE_LIST = 3;
+static const char EVENT_TYPE_FLOAT = 4;
+
+static const int kLogMsgHeaderSize = 28;
+
+static void write4Bytes(int val, vector<char>* buffer) {
+    buffer->push_back(static_cast<char>(val));
+    buffer->push_back(static_cast<char>((val >> 8) & 0xFF));
+    buffer->push_back(static_cast<char>((val >> 16) & 0xFF));
+    buffer->push_back(static_cast<char>((val >> 24) & 0xFF));
+}
+
+static void getSimpleLogMsgData(log_msg* msg) {
+    vector<char> buffer;
+    // stats_log tag id
+    write4Bytes(1937006964, &buffer);
+    buffer.push_back(EVENT_TYPE_LIST);
+    buffer.push_back(2);  // field counts;
+    buffer.push_back(EVENT_TYPE_INT);
+    write4Bytes(10 /* atom id */, &buffer);
+    buffer.push_back(EVENT_TYPE_INT);
+    write4Bytes(99 /* a value to log*/, &buffer);
+    buffer.push_back(EVENT_TYPE_LIST_STOP);
+
+    msg->entry_v1.len = buffer.size();
+    msg->entry.hdr_size = kLogMsgHeaderSize;
+    msg->entry_v1.sec = time(nullptr);
+    std::copy(buffer.begin(), buffer.end(), msg->buf + kLogMsgHeaderSize);
+}
+
+static void BM_LogEventCreation(benchmark::State& state) {
+    log_msg msg;
+    getSimpleLogMsgData(&msg);
+    while (state.KeepRunning()) {
+        benchmark::DoNotOptimize(LogEvent(msg));
+    }
+}
+BENCHMARK(BM_LogEventCreation);
+
+}  //  namespace statsd
+}  //  namespace os
+}  //  namespace android
diff --git a/cmds/statsd/benchmark/main.cpp b/cmds/statsd/benchmark/main.cpp
new file mode 100644
index 0000000..08921f3
--- /dev/null
+++ b/cmds/statsd/benchmark/main.cpp
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+#include <benchmark/benchmark.h>
+
+BENCHMARK_MAIN();
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 1b8efe0..edc9f2c 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -256,7 +256,7 @@
 
     // Then, check stats-data directory to see there's any file containing
     // ConfigMetricsReport from previous shutdowns to concatenate to reports.
-    StorageManager::appendConfigMetricsReport(STATS_DATA_DIR, proto);
+    StorageManager::appendConfigMetricsReport(proto);
 
     if (outData != nullptr) {
         outData->clear();
@@ -327,8 +327,8 @@
         vector<uint8_t> data;
         onDumpReportLocked(key, &data);
         // TODO: Add a guardrail to prevent accumulation of file on disk.
-        string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_DATA_DIR, key.GetUid(),
-                                        (long long)key.GetId(), time(nullptr));
+        string file_name = StringPrintf("%s/%ld_%d_%lld", STATS_DATA_DIR, time(nullptr),
+                                        key.GetUid(), (long long)key.GetId());
         StorageManager::writeFile(file_name.c_str(), &data[0], data.size());
     }
 }
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 8975c54..31994e1 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -24,6 +24,7 @@
 #include "guardrail/MemoryLeakTrackUtil.h"
 #include "guardrail/StatsdStats.h"
 #include "storage/StorageManager.h"
+#include "subscriber/SubscriberReporter.h"
 
 #include <android-base/file.h>
 #include <binder/IPCThreadState.h>
@@ -67,6 +68,7 @@
 void CompanionDeathRecipient::binderDied(const wp<IBinder>& who) {
     ALOGW("statscompanion service died");
     mAnomalyMonitor->setStatsCompanionService(nullptr);
+    SubscriberReporter::getInstance().setStatsCompanionService(nullptr);
 }
 
 // ======================================================================
@@ -683,6 +685,7 @@
     VLOG("StatsService::statsCompanionReady linking to statsCompanion.");
     IInterface::asBinder(statsCompanion)->linkToDeath(new CompanionDeathRecipient(mAnomalyMonitor));
     mAnomalyMonitor->setStatsCompanionService(statsCompanion);
+    SubscriberReporter::getInstance().setStatsCompanionService(statsCompanion);
 
     return Status::ok();
 }
@@ -745,7 +748,9 @@
 Status StatsService::removeConfiguration(int64_t key, bool* success) {
     IPCThreadState* ipc = IPCThreadState::self();
     if (checkCallingPermission(String16(kPermissionDump))) {
-        mConfigManager->RemoveConfig(ConfigKey(ipc->getCallingUid(), key));
+        ConfigKey configKey(ipc->getCallingUid(), key);
+        mConfigManager->RemoveConfig(configKey);
+        SubscriberReporter::getInstance().removeConfig(configKey);
         *success = true;
         return Status::ok();
     } else {
@@ -754,6 +759,42 @@
     }
 }
 
+Status StatsService::setBroadcastSubscriber(int64_t configId,
+                                            int64_t subscriberId,
+                                            const sp<android::IBinder>& intentSender,
+                                            bool* success) {
+    VLOG("StatsService::setBroadcastSubscriber called.");
+    IPCThreadState* ipc = IPCThreadState::self();
+    if (checkCallingPermission(String16(kPermissionDump))) {
+        ConfigKey configKey(ipc->getCallingUid(), configId);
+        SubscriberReporter::getInstance()
+                .setBroadcastSubscriber(configKey, subscriberId, intentSender);
+        *success = true;
+        return Status::ok();
+    } else {
+        *success = false;
+        return Status::fromExceptionCode(binder::Status::EX_SECURITY);
+    }
+}
+
+Status StatsService::unsetBroadcastSubscriber(int64_t configId,
+                                              int64_t subscriberId,
+                                              bool* success) {
+    VLOG("StatsService::unsetBroadcastSubscriber called.");
+    IPCThreadState* ipc = IPCThreadState::self();
+    if (checkCallingPermission(String16(kPermissionDump))) {
+        ConfigKey configKey(ipc->getCallingUid(), configId);
+        SubscriberReporter::getInstance()
+                .unsetBroadcastSubscriber(configKey, subscriberId);
+        *success = true;
+        return Status::ok();
+    } else {
+        *success = false;
+        return Status::fromExceptionCode(binder::Status::EX_SECURITY);
+    }
+}
+
+
 void StatsService::binderDied(const wp <IBinder>& who) {
 }
 
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 8d29970..ba6bd24 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -99,6 +99,21 @@
      */
     virtual Status removeConfiguration(int64_t key, bool* success) override;
 
+    /**
+     * Binder call to associate the given config's subscriberId with the given intentSender.
+     * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder).
+     */
+    virtual Status setBroadcastSubscriber(int64_t configId,
+                                          int64_t subscriberId,
+                                          const sp<android::IBinder>& intentSender,
+                                          bool* success) override;
+
+    /**
+     * Binder call to unassociate the given config's subscriberId with any intentSender.
+     */
+    virtual Status unsetBroadcastSubscriber(int64_t configId, int64_t subscriberId,
+                                            bool* success) override;
+
     // TODO: public for testing since statsd doesn't run when system starts. Change to private
     // later.
     /** Inform statsCompanion that statsd is ready. */
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index e34aed3..ded6c4c 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -21,6 +21,7 @@
 #include "external/Perfetto.h"
 #include "guardrail/StatsdStats.h"
 #include "frameworks/base/libs/incident/proto/android/os/header.pb.h"
+#include "subscriber/SubscriberReporter.h"
 
 #include <android/os/IIncidentManager.h>
 #include <android/os/IncidentReportArgs.h>
@@ -233,6 +234,7 @@
     }
 
     std::set<int> incidentdSections;
+
     for (const Subscription& subscription : mSubscriptions) {
         switch (subscription.subscriber_information_case()) {
             case Subscription::SubscriberInformationCase::kIncidentdDetails:
@@ -243,6 +245,10 @@
             case Subscription::SubscriberInformationCase::kPerfettoDetails:
                 CollectPerfettoTraceAndUploadToDropbox(subscription.perfetto_details());
                 break;
+            case Subscription::SubscriberInformationCase::kBroadcastSubscriberDetails:
+                SubscriberReporter::getInstance()
+                        .alertBroadcastSubscriber(mConfigKey, subscription, key);
+                break;
             default:
                 break;
         }
diff --git a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
index de7093d..33e55ab 100644
--- a/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/DurationAnomalyTracker.h
@@ -51,10 +51,8 @@
 
     // Declares an anomaly for each alarm in firedAlarms that belongs to this DurationAnomalyTracker
     // and removes it from firedAlarms.
-    // TODO: This will actually be called from a different thread, so make it thread-safe!
-    //          This means that almost every function in DurationAnomalyTracker needs to be locked.
-    //          But this should be done at the level of StatsLogProcessor, which needs to lock
-    //          mMetricsMangers anyway.
+    // 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,
             unordered_set<sp<const AnomalyAlarm>, SpHash<AnomalyAlarm>>& firedAlarms) override;
 
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 496c29b..61eeee3 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -186,8 +186,8 @@
     remove_saved_configs(key);
 
     // Then we save the latest config.
-    string file_name = StringPrintf("%s/%d-%lld-%ld", STATS_SERVICE_DIR, key.GetUid(),
-                                    (long long)key.GetId(), time(nullptr));
+    string file_name = StringPrintf("%s/%ld_%d_%lld", STATS_SERVICE_DIR, time(nullptr),
+                                    key.GetUid(), (long long)key.GetId());
     const int numBytes = config.ByteSize();
     vector<uint8_t> buffer(numBytes);
     config.SerializeToArray(&buffer[0], numBytes);
diff --git a/cmds/statsd/src/external/Perfetto.cpp b/cmds/statsd/src/external/Perfetto.cpp
index f7b33e7..1d8a968 100644
--- a/cmds/statsd/src/external/Perfetto.cpp
+++ b/cmds/statsd/src/external/Perfetto.cpp
@@ -20,6 +20,7 @@
 
 #include <android-base/unique_fd.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -65,11 +66,26 @@
 
         // Replace stdin with |readPipe| so the main process can write into it.
         if (dup2(readPipe.get(), STDIN_FILENO) < 0) _exit(1);
+        readPipe.reset();
+
+        // Replace stdout/stderr with /dev/null and close any other file
+        // descriptor. This is to avoid SELinux complaining about perfetto
+        // trying to access files accidentally left open by statsd (i.e. files
+        // that have been opened without the O_CLOEXEC flag).
+        int devNullFd = open("/dev/null", O_RDWR | O_CLOEXEC);
+        if (dup2(devNullFd, STDOUT_FILENO) < 0) _exit(2);
+        if (dup2(devNullFd, STDERR_FILENO) < 0) _exit(3);
+        close(devNullFd);
+        for (int i = 0; i < 1024; i++) {
+            if (i != STDIN_FILENO && i != STDOUT_FILENO && i != STDERR_FILENO) close(i);
+        }
+
         execl("/system/bin/perfetto", "perfetto", "--background", "--config", "-", "--dropbox",
               kDropboxTag, nullptr);
 
-        // execl() doesn't return in case of success, if we get here something failed.
-        _exit(1);
+        // execl() doesn't return in case of success, if we get here something
+        // failed.
+        _exit(4);
     }
 
     // Main process.
@@ -93,8 +109,8 @@
         return false;
     }
 
-    // This does NOT wait for the full duration of the trace. It just waits until the process
-    // has read the config from stdin and detached.
+    // This does NOT wait for the full duration of the trace. It just waits until
+    // the process has read the config from stdin and detached.
     int childStatus = 0;
     waitpid(pid, &childStatus, 0);
     if (!WIFEXITED(childStatus) || WEXITSTATUS(childStatus) != 0) {
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index 9178daa4..7cb48ea 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -74,6 +74,15 @@
     // Default cooldown time for a puller
     static const long kDefaultPullerCooldown = 1;
 
+    // Maximum age (30 days) that files on disk can exist in seconds.
+    static const int kMaxAgeSecond = 60 * 60 * 24 * 30;
+
+    // Maximum number of files (1000) that can be in stats directory on disk.
+    static const int kMaxFileNumber = 1000;
+
+    // Maximum size of all files that can be written to stats directory on disk.
+    static const int kMaxFileSize = 50 * 1024 * 1024;
+
     /**
      * Report a new config has been received and report the static stats about the config.
      *
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 34fa3c4..1ca793c 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -42,6 +42,7 @@
     mLogUid = msg.entry_v4.uid;
     init(mContext);
     if (mContext) {
+        // android_log_destroy will set mContext to NULL
         android_log_destroy(&mContext);
     }
 }
@@ -64,12 +65,17 @@
         mContext = create_android_log_parser(buffer, len);
         init(mContext);
         // destroy the context to save memory.
-        android_log_destroy(&mContext);
+        if (mContext) {
+            // android_log_destroy will set mContext to NULL
+            android_log_destroy(&mContext);
+        }
     }
 }
 
 LogEvent::~LogEvent() {
     if (mContext) {
+        // This is for the case when LogEvent is created using the test interface
+        // but init() isn't called.
         android_log_destroy(&mContext);
     }
 }
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 3d6984c..5a4efd4 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -156,7 +156,7 @@
     // This field is used when statsD wants to create log event object and write fields to it. After
     // calling init() function, this object would be destroyed to save memory usage.
     // When the log event is created from log msg, this field is never initiated.
-    android_log_context mContext;
+    android_log_context mContext = NULL;
 
     uint64_t mTimestampNs;
 
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index f127701..83b72d9 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -17,11 +17,15 @@
 #define DEBUG true  // STOPSHIP if true
 #include "Log.h"
 
-#include "storage/StorageManager.h"
 #include "android-base/stringprintf.h"
+#include "guardrail/StatsdStats.h"
+#include "storage/StorageManager.h"
 
 #include <android-base/file.h>
 #include <dirent.h>
+#include <private/android_filesystem_config.h>
+#include <fstream>
+#include <iostream>
 
 namespace android {
 namespace os {
@@ -31,6 +35,7 @@
 using android::util::FIELD_TYPE_MESSAGE;
 using std::map;
 
+#define STATS_DATA_DIR "/data/misc/stats-data"
 #define STATS_SERVICE_DIR "/data/misc/stats-service"
 
 // for ConfigMetricsReportList
@@ -39,12 +44,37 @@
 using android::base::StringPrintf;
 using std::unique_ptr;
 
+// Returns array of int64_t which contains timestamp in seconds, uid, and
+// configID.
+static void parseFileName(char* name, int64_t* result) {
+    int index = 0;
+    char* substr = strtok(name, "_");
+    while (substr != nullptr && index < 3) {
+        result[index] = StrToInt64(substr);
+        index++;
+        substr = strtok(nullptr, "_");
+    }
+    // When index ends before hitting 3, file name is corrupted. We
+    // intentionally put -1 at index 0 to indicate the error to caller.
+    // TODO: consider removing files with unexpected name format.
+    if (index < 3) {
+        result[0] = -1;
+    }
+}
+
+static string getFilePath(const char* path, int64_t timestamp, int64_t uid, int64_t configID) {
+    return StringPrintf("%s/%lld-%d-%lld", path, (long long)timestamp, (int)uid,
+                        (long long)configID);
+}
+
 void StorageManager::writeFile(const char* file, const void* buffer, int numBytes) {
     int fd = open(file, O_WRONLY | O_CREAT | O_CLOEXEC, S_IRUSR | S_IWUSR);
     if (fd == -1) {
         VLOG("Attempt to access %s but failed", file);
         return;
     }
+    trimToFit(STATS_SERVICE_DIR);
+    trimToFit(STATS_DATA_DIR);
 
     int result = write(fd, buffer, numBytes);
     if (result == numBytes) {
@@ -52,6 +82,12 @@
     } else {
         VLOG("Failed to write %s", file);
     }
+
+    result = fchown(fd, AID_STATSD, AID_STATSD);
+    if (result) {
+        VLOG("Failed to chown %s to statsd", file);
+    }
+
     close(fd);
 }
 
@@ -113,30 +149,20 @@
         if (name[0] == '.') continue;
         VLOG("file %s", name);
 
-        int index = 0;
-        int uid = 0;
-        int64_t configID = 0;
-        char* substr = strtok(name, "-");
-        // Timestamp lives at index 2 but we skip parsing it as it's not needed.
-        while (substr != nullptr && index < 2) {
-            if (index == 0) {
-                uid = atoi(substr);
-            } else if (index == 1) {
-                configID = StrToInt64(substr);
-            }
-            index++;
-            substr = strtok(nullptr, "-");
-        }
-        if (index < 2) continue;
+        int64_t result[3];
+        parseFileName(name, result);
+        if (result[0] == -1) continue;
+        int64_t uid = result[1];
+        int64_t configID = result[2];
 
-        sendBroadcast(ConfigKey(uid, configID));
+        sendBroadcast(ConfigKey((int)uid, configID));
     }
 }
 
-void StorageManager::appendConfigMetricsReport(const char* path, ProtoOutputStream& proto) {
-    unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
+void StorageManager::appendConfigMetricsReport(ProtoOutputStream& proto) {
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir);
     if (dir == NULL) {
-        VLOG("Path %s does not exist", path);
+        VLOG("Path %s does not exist", STATS_DATA_DIR);
         return;
     }
 
@@ -146,25 +172,13 @@
         if (name[0] == '.') continue;
         VLOG("file %s", name);
 
-        int index = 0;
-        int uid = 0;
-        int64_t configID = 0;
-        int64_t timestamp = 0;
-        char* substr = strtok(name, "-");
-        while (substr != nullptr && index < 3) {
-            if (index == 0) {
-                uid = atoi(substr);
-            } else if (index == 1) {
-                configID = StrToInt64(substr);
-            } else if (index == 2) {
-                timestamp = atoi(substr);
-            }
-            index++;
-            substr = strtok(nullptr, "-");
-        }
-        if (index < 3) continue;
-        string file_name = StringPrintf("%s/%d-%lld-%lld", STATS_SERVICE_DIR, uid,
-                                        (long long)configID, (long long)timestamp);
+        int64_t result[3];
+        parseFileName(name, result);
+        if (result[0] == -1) continue;
+        int64_t timestamp = result[0];
+        int64_t uid = result[1];
+        int64_t configID = result[2];
+        string file_name = getFilePath(STATS_DATA_DIR, timestamp, uid, configID);
         int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
         if (fd != -1) {
             string content;
@@ -186,6 +200,7 @@
         VLOG("no default config on disk");
         return;
     }
+    trimToFit(STATS_SERVICE_DIR);
 
     dirent* de;
     while ((de = readdir(dir.get()))) {
@@ -193,26 +208,13 @@
         if (name[0] == '.') continue;
         VLOG("file %s", name);
 
-        int index = 0;
-        int uid = 0;
-        int64_t configID = 0;
-        int64_t timestamp = 0;
-        char* substr = strtok(name, "-");
-        while (substr != nullptr && index < 3) {
-            if (index == 0) {
-                uid = atoi(substr);
-            } else if (index == 1) {
-                configID = StrToInt64(substr);
-            } else if (index == 2) {
-                timestamp = atoi(substr);
-            }
-            index++;
-            substr = strtok(nullptr, "-");
-        }
-        if (index < 3) continue;
-
-        string file_name = StringPrintf("%s/%d-%lld-%lld", STATS_SERVICE_DIR, uid,
-                                        (long long)configID, (long long)timestamp);
+        int64_t result[3];
+        parseFileName(name, result);
+        if (result[0] == -1) continue;
+        int64_t timestamp = result[0];
+        int64_t uid = result[1];
+        int64_t configID = result[2];
+        string file_name = getFilePath(STATS_SERVICE_DIR, timestamp, uid, configID);
         int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
         if (fd != -1) {
             string content;
@@ -220,7 +222,7 @@
                 StatsdConfig config;
                 if (config.ParseFromString(content)) {
                     configsMap[ConfigKey(uid, configID)] = config;
-                    VLOG("map key uid=%d|configID=%lld", uid, (long long)configID);
+                    VLOG("map key uid=%lld|configID=%lld", (long long)uid, (long long)configID);
                 }
             }
             close(fd);
@@ -228,6 +230,67 @@
     }
 }
 
+void StorageManager::trimToFit(const char* path) {
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(path), closedir);
+    if (dir == NULL) {
+        VLOG("Path %s does not exist", path);
+        return;
+    }
+    dirent* de;
+    int totalFileSize = 0;
+    vector<string> fileNames;
+    while ((de = readdir(dir.get()))) {
+        char* name = de->d_name;
+        if (name[0] == '.') continue;
+
+        int64_t result[3];
+        parseFileName(name, result);
+        if (result[0] == -1) continue;
+        int64_t timestamp = result[0];
+        int64_t uid = result[1];
+        int64_t configID = result[2];
+        string file_name = getFilePath(path, timestamp, uid, configID);
+
+        // Check for timestamp and delete if it's too old.
+        long fileAge = time(nullptr) - timestamp;
+        if (fileAge > StatsdStats::kMaxAgeSecond) {
+            deleteFile(file_name.c_str());
+        }
+
+        fileNames.push_back(file_name);
+        ifstream file(file_name.c_str(), ifstream::in | ifstream::binary);
+        if (file.is_open()) {
+            file.seekg(0, ios::end);
+            int fileSize = file.tellg();
+            file.close();
+            totalFileSize += fileSize;
+        }
+    }
+
+    if (fileNames.size() > StatsdStats::kMaxFileNumber ||
+        totalFileSize > StatsdStats::kMaxFileSize) {
+        // Reverse sort to effectively remove from the back (oldest entries).
+        // This will sort files in reverse-chronological order.
+        sort(fileNames.begin(), fileNames.end(), std::greater<std::string>());
+    }
+
+    // Start removing files from oldest to be under the limit.
+    while (fileNames.size() > 0 && (fileNames.size() > StatsdStats::kMaxFileNumber ||
+                                    totalFileSize > StatsdStats::kMaxFileSize)) {
+        string file_name = fileNames.at(fileNames.size() - 1);
+        ifstream file(file_name.c_str(), ifstream::in | ifstream::binary);
+        if (file.is_open()) {
+            file.seekg(0, ios::end);
+            int fileSize = file.tellg();
+            file.close();
+            totalFileSize -= fileSize;
+        }
+
+        deleteFile(file_name.c_str());
+        fileNames.pop_back();
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h
index f9988fe..d319674 100644
--- a/cmds/statsd/src/storage/StorageManager.h
+++ b/cmds/statsd/src/storage/StorageManager.h
@@ -61,12 +61,18 @@
      * Appends ConfigMetricsReport found on disk to the specific proto and
      * delete it.
      */
-    static void appendConfigMetricsReport(const char* path, ProtoOutputStream& proto);
+    static void appendConfigMetricsReport(ProtoOutputStream& proto);
 
     /**
      * Call to load the saved configs from disk.
      */
     static void readConfigFromDisk(std::map<ConfigKey, StatsdConfig>& configsMap);
+
+    /**
+     * Trims files in the provided directory to limit the total size, number of
+     * files, accumulation of outdated files.
+     */
+    static void trimToFit(const char* dir);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
new file mode 100644
index 0000000..f912e4b
--- /dev/null
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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 false  // STOPSHIP if true
+#include "Log.h"
+
+#include "SubscriberReporter.h"
+
+using android::IBinder;
+using std::lock_guard;
+using std::unordered_map;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey,
+                                                int64_t subscriberId,
+                                                const sp<IBinder>& intentSender) {
+    VLOG("SubscriberReporter::setBroadcastSubscriber called.");
+    lock_guard<std::mutex> lock(mLock);
+    mIntentMap[configKey][subscriberId] = intentSender;
+}
+
+void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey,
+                                                  int64_t subscriberId) {
+    VLOG("SubscriberReporter::unsetBroadcastSubscriber called.");
+    lock_guard<std::mutex> lock(mLock);
+    auto subscriberMapIt = mIntentMap.find(configKey);
+    if (subscriberMapIt != mIntentMap.end()) {
+        subscriberMapIt->second.erase(subscriberId);
+        if (subscriberMapIt->second.empty()) {
+            mIntentMap.erase(configKey);
+        }
+    }
+}
+
+void SubscriberReporter::removeConfig(const ConfigKey& configKey) {
+    VLOG("SubscriberReporter::removeConfig called.");
+    lock_guard<std::mutex> lock(mLock);
+    mIntentMap.erase(configKey);
+}
+
+void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey,
+                                                  const Subscription& subscription,
+                                                  const HashableDimensionKey& dimKey) const {
+    // Reminder about ids:
+    //  subscription id - name of the Subscription (that ties the Alert to the broadcast)
+    //  subscription rule_id - the name of the Alert (that triggers the broadcast)
+    //  subscriber_id - name of the PendingIntent to use to send the broadcast
+    //  config uid - the uid that uploaded the config (and therefore gave the PendingIntent,
+    //                 although the intent may be to broadcast to a different uid)
+    //  config id - the name of this config (for this particular uid)
+
+    VLOG("SubscriberReporter::alertBroadcastSubscriber called.");
+    lock_guard<std::mutex> lock(mLock);
+
+    if (!subscription.has_broadcast_subscriber_details()
+            || !subscription.broadcast_subscriber_details().has_subscriber_id()) {
+        ALOGE("Broadcast subscriber does not have an id.");
+        return;
+    }
+    int64_t subscriberId = subscription.broadcast_subscriber_details().subscriber_id();
+
+    auto it1 = mIntentMap.find(configKey);
+    if (it1 == mIntentMap.end()) {
+        ALOGW("Cannot inform subscriber for missing config key %s ", configKey.ToString().c_str());
+        return;
+    }
+    auto it2 = it1->second.find(subscriberId);
+    if (it2 == it1->second.end()) {
+        ALOGW("Cannot inform subscriber of config %s for missing subscriberId %lld ",
+                configKey.ToString().c_str(), (long long)subscriberId);
+        return;
+    }
+    sendBroadcastLocked(it2->second, configKey, subscription, dimKey);
+}
+
+void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender,
+                                             const ConfigKey& configKey,
+                                             const Subscription& subscription,
+                                             const HashableDimensionKey& dimKey) const {
+    VLOG("SubscriberReporter::sendBroadcastLocked called.");
+    if (mStatsCompanionService == nullptr) {
+        ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService.");
+        return;
+    }
+    mStatsCompanionService->sendSubscriberBroadcast(intentSender,
+                                                    configKey.GetUid(),
+                                                    configKey.GetId(),
+                                                    subscription.id(),
+                                                    subscription.rule_id(),
+                                                    protoToStatsDimensionsValue(dimKey));
+}
+
+StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue(
+        const HashableDimensionKey& dimKey) {
+    return protoToStatsDimensionsValue(dimKey.getDimensionsValue());
+}
+
+StatsDimensionsValue SubscriberReporter::protoToStatsDimensionsValue(
+        const DimensionsValue& protoDimsVal) {
+    int32_t field = protoDimsVal.field();
+
+    switch (protoDimsVal.value_case()) {
+        case DimensionsValue::ValueCase::kValueStr:
+            return StatsDimensionsValue(field, String16(protoDimsVal.value_str().c_str()));
+        case DimensionsValue::ValueCase::kValueInt:
+            return StatsDimensionsValue(field, static_cast<int32_t>(protoDimsVal.value_int()));
+        case DimensionsValue::ValueCase::kValueLong:
+            return StatsDimensionsValue(field, static_cast<int64_t>(protoDimsVal.value_long()));
+        case DimensionsValue::ValueCase::kValueBool:
+            return StatsDimensionsValue(field, static_cast<bool>(protoDimsVal.value_bool()));
+        case DimensionsValue::ValueCase::kValueFloat:
+            return StatsDimensionsValue(field, static_cast<float>(protoDimsVal.value_float()));
+        case DimensionsValue::ValueCase::kValueTuple:
+            {
+                int sz = protoDimsVal.value_tuple().dimensions_value_size();
+                std::vector<StatsDimensionsValue> sdvVec(sz);
+                for (int i = 0; i < sz; i++) {
+                    sdvVec[i] = protoToStatsDimensionsValue(
+                            protoDimsVal.value_tuple().dimensions_value(i));
+                }
+                return StatsDimensionsValue(field, sdvVec);
+            }
+        default:
+            ALOGW("protoToStatsDimensionsValue failed: illegal type.");
+            return StatsDimensionsValue();
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
new file mode 100644
index 0000000..5bb458a
--- /dev/null
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -0,0 +1,117 @@
+/*
+ * 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 <android/os/IStatsCompanionService.h>
+#include <utils/RefBase.h>
+
+#include "config/ConfigKey.h"
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // subscription
+#include "frameworks/base/cmds/statsd/src/stats_log.pb.h"  // DimensionsValue
+#include "android/os/StatsDimensionsValue.h"
+#include "HashableDimensionKey.h"
+
+#include <mutex>
+#include <unordered_map>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+// Reports information to subscribers.
+// Single instance shared across the process. All methods are thread safe.
+class SubscriberReporter {
+public:
+    /** Get (singleton) instance of SubscriberReporter. */
+    static SubscriberReporter& getInstance() {
+        static SubscriberReporter subscriberReporter;
+        return subscriberReporter;
+    }
+
+    ~SubscriberReporter(){};
+    SubscriberReporter(SubscriberReporter const&) = delete;
+    void operator=(SubscriberReporter const&) = delete;
+
+    /**
+     * Tells SubscriberReporter what IStatsCompanionService to use.
+     * May be nullptr, but SubscriberReporter will not send broadcasts for any calls
+     * to alertBroadcastSubscriber that occur while nullptr.
+     */
+    void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
+        std::lock_guard<std::mutex> lock(mLock);
+        sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
+        mStatsCompanionService = statsCompanionService;
+    }
+
+    /**
+     * Stores the given intentSender, associating it with the given (configKey, subscriberId) pair.
+     * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder).
+     */
+    void setBroadcastSubscriber(const ConfigKey& configKey,
+                                int64_t subscriberId,
+                                const sp<android::IBinder>& intentSender);
+
+    /**
+     * Erases any intentSender information from the given (configKey, subscriberId) pair.
+     */
+    void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId);
+
+    /** Remove all information stored by SubscriberReporter about the given config. */
+    void removeConfig(const ConfigKey& configKey);
+
+    /**
+     * Sends a broadcast via the intentSender previously stored for the
+     * given (configKey, subscriberId) pair by setBroadcastSubscriber.
+     * Information about the subscriber, as well as information extracted from the dimKey, is sent.
+     */
+    void alertBroadcastSubscriber(const ConfigKey& configKey,
+                                  const Subscription& subscription,
+                                  const HashableDimensionKey& dimKey) const;
+
+private:
+    SubscriberReporter() {};
+
+    mutable std::mutex mLock;
+
+    /** Binder interface for communicating with StatsCompanionService. */
+    sp<IStatsCompanionService> mStatsCompanionService = nullptr;
+
+    /** Maps <ConfigKey, SubscriberId> -> IBinder (which represents an IIntentSender). */
+    std::unordered_map<ConfigKey,
+            std::unordered_map<int64_t, sp<android::IBinder>>> mIntentMap;
+
+    /**
+     * Sends a broadcast via the given intentSender (using mStatsCompanionService), along
+     * with the information in the other parameters.
+     */
+    void sendBroadcastLocked(const sp<android::IBinder>& intentSender,
+                             const ConfigKey& configKey,
+                             const Subscription& subscription,
+                             const HashableDimensionKey& dimKey) const;
+
+    /** Converts a stats_log.proto DimensionsValue to a StatsDimensionsValue. */
+    static StatsDimensionsValue protoToStatsDimensionsValue(
+            const DimensionsValue& protoDimsVal);
+
+    /** Converts a HashableDimensionKey to a StatsDimensionsValue. */
+    static StatsDimensionsValue protoToStatsDimensionsValue(
+            const HashableDimensionKey& dimKey);
+};
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
index 4f9032f..d39aa1d 100644
--- a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
@@ -16,12 +16,12 @@
 package com.android.statsd.dogfood;
 
 import android.app.Activity;
+import android.app.StatsManager;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.util.Log;
 import android.util.StatsLog;
-import android.util.StatsManager;
 import android.view.View;
 import android.widget.TextView;
 import android.widget.Toast;
diff --git a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
index 26c1c72..652f6b2 100644
--- a/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
+++ b/cmds/statsd/tools/loadtest/src/com/android/statsd/loadtest/LoadtestActivity.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
+import android.app.StatsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -34,7 +35,6 @@
 import android.text.TextWatcher;
 import android.util.Log;
 import android.util.StatsLog;
-import android.util.StatsManager;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
 import android.view.MotionEvent;
diff --git a/config/hiddenapi-blacklist.txt b/config/hiddenapi-blacklist.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/config/hiddenapi-blacklist.txt
diff --git a/config/hiddenapi-dark-greylist.txt b/config/hiddenapi-dark-greylist.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/config/hiddenapi-dark-greylist.txt
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index cd029c0..f0ef49f 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6330,6 +6330,8 @@
         } else {
             writer.print(prefix); writer.println("No AutofillManager");
         }
+
+        ResourcesManager.getInstance().dump(prefix, writer);
     }
 
     /**
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index ac6cba1..5d0143a 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.annotation.NonNull;
+import android.app.ActivityManager.StackInfo;
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
@@ -38,6 +39,8 @@
 
 import dalvik.system.CloseGuard;
 
+import java.util.List;
+
 /**
  * Activity container that allows launching activities into itself and does input forwarding.
  * <p>Creation of this view is only allowed to callers who have
@@ -58,10 +61,13 @@
     private final SurfaceCallback mSurfaceCallback;
     private StateCallback mActivityViewCallback;
 
+    private IActivityManager mActivityManager;
     private IInputForwarder mInputForwarder;
     // Temp container to store view coordinates on screen.
     private final int[] mLocationOnScreen = new int[2];
 
+    private TaskStackListener mTaskStackListener;
+
     private final CloseGuard mGuard = CloseGuard.get();
     private boolean mOpened; // Protected by mGuard.
 
@@ -76,6 +82,7 @@
     public ActivityView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
+        mActivityManager = ActivityManager.getService();
         mSurfaceView = new SurfaceView(context);
         mSurfaceCallback = new SurfaceCallback();
         mSurfaceView.getHolder().addCallback(mSurfaceCallback);
@@ -303,6 +310,12 @@
 
         mInputForwarder = InputManager.getInstance().createInputForwarder(
                 mVirtualDisplay.getDisplay().getDisplayId());
+        mTaskStackListener = new TaskBackgroundChangeListener();
+        try {
+            mActivityManager.registerTaskStackListener(mTaskStackListener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register task stack listener", e);
+        }
     }
 
     private void performRelease() {
@@ -317,6 +330,15 @@
         }
         cleanTapExcludeRegion();
 
+        if (mTaskStackListener != null) {
+            try {
+                mActivityManager.unregisterTaskStackListener(mTaskStackListener);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to unregister task stack listener", e);
+            }
+            mTaskStackListener = null;
+        }
+
         final boolean displayReleased;
         if (mVirtualDisplay != null) {
             mVirtualDisplay.release();
@@ -369,4 +391,42 @@
             super.finalize();
         }
     }
+
+    /**
+     * A task change listener that detects background color change of the topmost stack on our
+     * virtual display and updates the background of the surface view. This background will be shown
+     * when surface view is resized, but the app hasn't drawn its content in new size yet.
+     */
+    private class TaskBackgroundChangeListener extends TaskStackListener {
+
+        @Override
+        public void onTaskDescriptionChanged(int taskId, ActivityManager.TaskDescription td)
+                throws RemoteException {
+            if (mVirtualDisplay == null) {
+                return;
+            }
+
+            // Find the topmost task on our virtual display - it will define the background
+            // color of the surface view during resizing.
+            final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
+            final List<StackInfo> stackInfoList = mActivityManager.getAllStackInfos();
+
+            // Iterate through stacks from top to bottom.
+            final int stackCount = stackInfoList.size();
+            for (int i = 0; i < stackCount; i++) {
+                final StackInfo stackInfo = stackInfoList.get(i);
+                // Only look for stacks on our virtual display.
+                if (stackInfo.displayId != displayId) {
+                    continue;
+                }
+                // Found the topmost stack on target display. Now check if the topmost task's
+                // description changed.
+                if (taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+                    mSurfaceView.setResizeBackgroundColor(td.getBackgroundColor());
+                }
+                break;
+            }
+        }
+    }
+
 }
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 7ca6802..e923fb2 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -263,8 +263,10 @@
     public static final int OP_REQUEST_DELETE_PACKAGES = 72;
     /** @hide Bind an accessibility service. */
     public static final int OP_BIND_ACCESSIBILITY_SERVICE = 73;
+    /** @hide Continue handover of a call from another app */
+    public static final int OP_ACCEPT_HANDOVER = 74;
     /** @hide */
-    public static final int _NUM_OP = 74;
+    public static final int _NUM_OP = 75;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -378,7 +380,13 @@
     /** Answer incoming phone calls */
     public static final String OPSTR_ANSWER_PHONE_CALLS
             = "android:answer_phone_calls";
-
+    /**
+     * Accept call handover
+     * @hide
+     */
+    @SystemApi @TestApi
+    public static final String OPSTR_ACCEPT_HANDOVER
+            = "android:accept_handover";
     /** @hide */
     @SystemApi @TestApi
     public static final String OPSTR_GPS = "android:gps";
@@ -528,6 +536,7 @@
             OP_USE_SIP,
             OP_PROCESS_OUTGOING_CALLS,
             OP_ANSWER_PHONE_CALLS,
+            OP_ACCEPT_HANDOVER,
             // Microphone
             OP_RECORD_AUDIO,
             // Camera
@@ -626,6 +635,7 @@
             OP_CHANGE_WIFI_STATE,
             OP_REQUEST_DELETE_PACKAGES,
             OP_BIND_ACCESSIBILITY_SERVICE,
+            OP_ACCEPT_HANDOVER,
     };
 
     /**
@@ -706,6 +716,7 @@
             OPSTR_CHANGE_WIFI_STATE,
             OPSTR_REQUEST_DELETE_PACKAGES,
             OPSTR_BIND_ACCESSIBILITY_SERVICE,
+            OPSTR_ACCEPT_HANDOVER,
     };
 
     /**
@@ -787,6 +798,7 @@
             "CHANGE_WIFI_STATE",
             "REQUEST_DELETE_PACKAGES",
             "BIND_ACCESSIBILITY_SERVICE",
+            "ACCEPT_HANDOVER",
     };
 
     /**
@@ -868,6 +880,7 @@
             Manifest.permission.CHANGE_WIFI_STATE,
             Manifest.permission.REQUEST_DELETE_PACKAGES,
             Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
+            Manifest.permission.ACCEPT_HANDOVER,
     };
 
     /**
@@ -950,6 +963,7 @@
             null, // OP_CHANGE_WIFI_STATE
             null, // REQUEST_DELETE_PACKAGES
             null, // OP_BIND_ACCESSIBILITY_SERVICE
+            null, // ACCEPT_HANDOVER
     };
 
     /**
@@ -1031,6 +1045,7 @@
             false, // OP_CHANGE_WIFI_STATE
             false, // OP_REQUEST_DELETE_PACKAGES
             false, // OP_BIND_ACCESSIBILITY_SERVICE
+            false, // ACCEPT_HANDOVER
     };
 
     /**
@@ -1111,6 +1126,7 @@
             AppOpsManager.MODE_ALLOWED,  // OP_CHANGE_WIFI_STATE
             AppOpsManager.MODE_ALLOWED,  // REQUEST_DELETE_PACKAGES
             AppOpsManager.MODE_ALLOWED,  // OP_BIND_ACCESSIBILITY_SERVICE
+            AppOpsManager.MODE_ALLOWED,  // ACCEPT_HANDOVER
     };
 
     /**
@@ -1195,6 +1211,7 @@
             false, // OP_CHANGE_WIFI_STATE
             false, // OP_REQUEST_DELETE_PACKAGES
             false, // OP_BIND_ACCESSIBILITY_SERVICE
+            false, // ACCEPT_HANDOVER
     };
 
     /**
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 4914ffa..ea94042 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -913,14 +913,14 @@
 
     /** @hide */
     @Override
-    public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+    public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
         if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
             throw new AndroidRuntimeException(
                     "Calling startActivities() from outside of an Activity "
                     + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent."
                     + " Is this really what you want?");
         }
-        mMainThread.getInstrumentation().execStartActivitiesAsUser(
+        return mMainThread.getInstrumentation().execStartActivitiesAsUser(
                 getOuterContext(), mMainThread.getApplicationThread(), null,
                 (Activity) null, intents, options, userHandle.getIdentifier());
     }
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 4a85efd..3aeef14 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -49,11 +49,13 @@
      *
      * @param callbackBinder Binder on which to indicate operation completion,
      *        passed here as a convenience to the agent.
+     *
+     * @param transportFlags Flags with additional information about the transport.
      */
     void doBackup(in ParcelFileDescriptor oldState,
             in ParcelFileDescriptor data,
             in ParcelFileDescriptor newState,
-            long quotaBytes, int token, IBackupManager callbackBinder);
+            long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags);
 
     /**
      * Restore an entire data snapshot to the application.
@@ -100,13 +102,17 @@
      *
      * @param callbackBinder Binder on which to indicate operation completion,
      *        passed here as a convenience to the agent.
+     *
+     * @param transportFlags Flags with additional information about transport.
      */
-    void doFullBackup(in ParcelFileDescriptor data, long quotaBytes, int token, IBackupManager callbackBinder);
+    void doFullBackup(in ParcelFileDescriptor data, long quotaBytes, int token,
+            IBackupManager callbackBinder, int transportFlags);
 
     /**
      * Estimate how much data a full backup will deliver
      */
-    void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder);
+    void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder,
+            int transportFlags);
 
     /**
      * Tells the application agent that the backup data size exceeded current transport quota.
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 3c38a4e..f90b276 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1688,9 +1688,13 @@
      * {@link ActivityMonitor} objects only match against the first activity in
      * the array.
      *
+     * @return The corresponding flag {@link ActivityManager#START_CANCELED},
+     *         {@link ActivityManager#START_SUCCESS} etc. indicating whether the launch was
+     *         successful.
+     *
      * {@hide}
      */
-    public void execStartActivitiesAsUser(Context who, IBinder contextThread,
+    public int execStartActivitiesAsUser(Context who, IBinder contextThread,
             IBinder token, Activity target, Intent[] intents, Bundle options,
             int userId) {
         IApplicationThread whoThread = (IApplicationThread) contextThread;
@@ -1705,11 +1709,11 @@
                     }
                     if (result != null) {
                         am.mHits++;
-                        return;
+                        return ActivityManager.START_CANCELED;
                     } else if (am.match(who, null, intents[0])) {
                         am.mHits++;
                         if (am.isBlocking()) {
-                            return;
+                            return ActivityManager.START_CANCELED;
                         }
                         break;
                     }
@@ -1727,6 +1731,7 @@
                 .startActivities(whoThread, who.getBasePackageName(), intents, resolvedTypes,
                         token, options, userId);
             checkStartActivityResult(result, intents[0]);
+            return result;
         } catch (RemoteException e) {
             throw new RuntimeException("Failure from system", e);
         }
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index d6fddfc..0b5b363 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -200,6 +200,16 @@
      */
     private static final int MAX_REPLY_HISTORY = 5;
 
+
+    /**
+     * If the notification contained an unsent draft for a RemoteInput when the user clicked on it,
+     * we're adding the draft as a String extra to the {@link #contentIntent} using this key.
+     *
+     * <p>Apps may use this extra to prepopulate text fields in the app, where the user usually
+     * sends messages.</p>
+     */
+    public static final String EXTRA_REMOTE_INPUT_DRAFT = "android.remoteInputDraft";
+
     /**
      * A timestamp related to this notification, in milliseconds since the epoch.
      *
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/StatsManager.java b/core/java/android/app/StatsManager.java
new file mode 100644
index 0000000..963fc77
--- /dev/null
+++ b/core/java/android/app/StatsManager.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 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.app;
+
+import android.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.os.IBinder;
+import android.os.IStatsManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+/**
+ * API for statsd clients to send configurations and retrieve data.
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsManager extends android.util.StatsManager { // TODO: Remove the extends.
+    IStatsManager mService;
+    private static final String TAG = "StatsManager";
+
+    /** Long extra of uid that added the relevant stats config. */
+    public static final String EXTRA_STATS_CONFIG_UID =
+            "android.app.extra.STATS_CONFIG_UID";
+    /** Long extra of the relevant stats config's configKey. */
+    public static final String EXTRA_STATS_CONFIG_KEY =
+            "android.app.extra.STATS_CONFIG_KEY";
+    /** Long extra of the relevant statsd_config.proto's Subscription.id. */
+    public static final String EXTRA_STATS_SUBSCRIPTION_ID =
+            "android.app.extra.STATS_SUBSCRIPTION_ID";
+    /** Long extra of the relevant statsd_config.proto's Subscription.rule_id. */
+    public static final String EXTRA_STATS_SUBSCRIPTION_RULE_ID =
+            "android.app.extra.STATS_SUBSCRIPTION_RULE_ID";
+    /**
+     * Extra of a {@link android.os.StatsDimensionsValue} representing sliced dimension value
+     * information.
+     */
+    public static final String EXTRA_STATS_DIMENSIONS_VALUE =
+            "android.app.extra.STATS_DIMENSIONS_VALUE";
+
+    /**
+     * Constructor for StatsManagerClient.
+     *
+     * @hide
+     */
+    public StatsManager() {
+    }
+
+    /**
+     * Clients can send a configuration and simultaneously registers the name of a broadcast
+     * receiver that listens for when it should request data.
+     *
+     * @param configKey An arbitrary integer that allows clients to track the configuration.
+     * @param config    Wire-encoded StatsDConfig proto that specifies metrics (and all
+     *                  dependencies eg, conditions and matchers).
+     * @param pkg       The package name to receive the broadcast.
+     * @param cls       The name of the class that receives the broadcast.
+     * @return true if successful
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public boolean addConfiguration(long configKey, byte[] config, String pkg, String cls) {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    Slog.d(TAG, "Failed to find statsd when adding configuration");
+                    return false;
+                }
+                return service.addConfiguration(configKey, config, pkg, cls);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Failed to connect to statsd when adding configuration");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Remove a configuration from logging.
+     *
+     * @param configKey Configuration key to remove.
+     * @return true if successful
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public boolean removeConfiguration(long configKey) {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    Slog.d(TAG, "Failed to find statsd when removing configuration");
+                    return false;
+                }
+                return service.removeConfiguration(configKey);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Failed to connect to statsd when removing configuration");
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Set the PendingIntent to be used when broadcasting subscriber information to the given
+     * subscriberId within the given config.
+     *
+     * <p>
+     * Suppose that the calling uid has added a config with key configKey, and that in this config
+     * it is specified that when a particular anomaly is detected, a broadcast should be sent to
+     * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
+     * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
+     * when the anomaly is detected.
+     *
+     * <p>
+     * When statsd sends the broadcast, the PendingIntent will used to send an intent with
+     * information of
+     *   {@link #EXTRA_STATS_CONFIG_UID},
+     *   {@link #EXTRA_STATS_CONFIG_KEY},
+     *   {@link #EXTRA_STATS_SUBSCRIPTION_ID},
+     *   {@link #EXTRA_STATS_SUBSCRIPTION_RULE_ID}, and
+     *   {@link #EXTRA_STATS_DIMENSIONS_VALUE}.
+     *
+     * <p>
+     * This function can only be called by the owner (uid) of the config. It must be called each
+     * time statsd starts. The config must have been added first (via addConfiguration()).
+     *
+     * @param configKey The integer naming the config to which this subscriber is attached.
+     * @param subscriberId ID of the subscriber, as used in the config.
+     * @param pendingIntent the PendingIntent to use when broadcasting info to the subscriber
+     *                      associated with the given subscriberId. May be null, in which case
+     *                      it undoes any previous setting of this subscriberId.
+     * @return true if successful
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public boolean setBroadcastSubscriber(long configKey,
+                                          long subscriberId,
+                                          PendingIntent pendingIntent) {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    Slog.w(TAG, "Failed to find statsd when adding broadcast subscriber");
+                    return false;
+                }
+                if (pendingIntent != null) {
+                    // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
+                    IBinder intentSender = pendingIntent.getTarget().asBinder();
+                    return service.setBroadcastSubscriber(configKey, subscriberId, intentSender);
+                } else {
+                    return service.unsetBroadcastSubscriber(configKey, subscriberId);
+                }
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
+                return false;
+            }
+        }
+    }
+
+    /**
+     * Clients can request data with a binder call. This getter is destructive and also clears
+     * the retrieved metrics from statsd memory.
+     *
+     * @param configKey Configuration key to retrieve data from.
+     * @return Serialized ConfigMetricsReportList proto. Returns null on failure.
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public byte[] getData(long configKey) {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    Slog.d(TAG, "Failed to find statsd when getting data");
+                    return null;
+                }
+                return service.getData(configKey);
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Failed to connecto statsd when getting data");
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Clients can request metadata for statsd. Will contain stats across all configurations but not
+     * the actual metrics themselves (metrics must be collected via {@link #getData(String)}.
+     * This getter is not destructive and will not reset any metrics/counters.
+     *
+     * @return Serialized StatsdStatsReport proto. Returns null on failure.
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public byte[] getMetadata() {
+        synchronized (this) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    Slog.d(TAG, "Failed to find statsd when getting metadata");
+                    return null;
+                }
+                return service.getMetadata();
+            } catch (RemoteException e) {
+                Slog.d(TAG, "Failed to connecto statsd when getting metadata");
+                return null;
+            }
+        }
+    }
+
+    private class StatsdDeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            synchronized (this) {
+                mService = null;
+            }
+        }
+    }
+
+    private IStatsManager getIStatsManagerLocked() throws RemoteException {
+        if (mService != null) {
+            return mService;
+        }
+        mService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+        if (mService != null) {
+            mService.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
+        }
+        return mService;
+    }
+}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index fb8d101..4310434 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -141,7 +141,6 @@
 import android.telephony.euicc.EuiccCardManager;
 import android.telephony.euicc.EuiccManager;
 import android.util.Log;
-import android.util.StatsManager;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
diff --git a/core/java/android/app/TaskStackBuilder.java b/core/java/android/app/TaskStackBuilder.java
index bab993f..ab59747 100644
--- a/core/java/android/app/TaskStackBuilder.java
+++ b/core/java/android/app/TaskStackBuilder.java
@@ -213,13 +213,13 @@
      * Start the task stack constructed by this builder.
      * @hide
      */
-    public void startActivities(Bundle options, UserHandle userHandle) {
+    public int startActivities(Bundle options, UserHandle userHandle) {
         if (mIntents.isEmpty()) {
             throw new IllegalStateException(
                     "No intents added to TaskStackBuilder; cannot startActivities");
         }
 
-        mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle);
+        return mSourceContext.startActivitiesAsUser(getIntents(), options, userHandle);
     }
 
     /**
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 861cb9a..72eb494 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -143,6 +143,26 @@
     /** @hide */
     public static final int TYPE_SYMLINK = 3;
 
+    /**
+     * Flag for {@link BackupDataOutput#getTransportFlags()} and
+     * {@link FullBackupDataOutput#getTransportFlags()} only.
+     *
+     * <p>The transport has client-side encryption enabled. i.e., the user's backup has been
+     * encrypted with a key known only to the device, and not to the remote storage solution. Even
+     * if an attacker had root access to the remote storage provider they should not be able to
+     * decrypt the user's backup data.
+     */
+    public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1;
+
+    /**
+     * Flag for {@link BackupDataOutput#getTransportFlags()} and
+     * {@link FullBackupDataOutput#getTransportFlags()} only.
+     *
+     * <p>The transport is for a device-to-device transfer. There is no third party or intermediate
+     * storage. The user's backup data is sent directly to another device over e.g., USB or WiFi.
+     */
+    public static final int FLAG_DEVICE_TO_DEVICE_TRANSFER = 2;
+
     Handler mHandler = null;
 
     Handler getHandler() {
@@ -920,12 +940,14 @@
         public void doBackup(ParcelFileDescriptor oldState,
                 ParcelFileDescriptor data,
                 ParcelFileDescriptor newState,
-                long quotaBytes, int token, IBackupManager callbackBinder) throws RemoteException {
+                long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags)
+                throws RemoteException {
             // Ensure that we're running with the app's normal permission level
             long ident = Binder.clearCallingIdentity();
 
             if (DEBUG) Log.v(TAG, "doBackup() invoked");
-            BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor(), quotaBytes);
+            BackupDataOutput output = new BackupDataOutput(
+                    data.getFileDescriptor(), quotaBytes, transportFlags);
 
             try {
                 BackupAgent.this.onBackup(oldState, output, newState);
@@ -999,7 +1021,7 @@
 
         @Override
         public void doFullBackup(ParcelFileDescriptor data,
-                long quotaBytes, int token, IBackupManager callbackBinder) {
+                long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) {
             // Ensure that we're running with the app's normal permission level
             long ident = Binder.clearCallingIdentity();
 
@@ -1010,7 +1032,8 @@
             waitForSharedPrefs();
 
             try {
-                BackupAgent.this.onFullBackup(new FullBackupDataOutput(data, quotaBytes));
+                BackupAgent.this.onFullBackup(new FullBackupDataOutput(
+                        data, quotaBytes, transportFlags));
             } catch (IOException ex) {
                 Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
                 throw new RuntimeException(ex);
@@ -1044,10 +1067,12 @@
             }
         }
 
-        public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder) {
+        public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder,
+                int transportFlags) {
             // Ensure that we're running with the app's normal permission level
             final long ident = Binder.clearCallingIdentity();
-            FullBackupDataOutput measureOutput = new FullBackupDataOutput(quotaBytes);
+            FullBackupDataOutput measureOutput =
+                    new FullBackupDataOutput(quotaBytes, transportFlags);
 
             waitForSharedPrefs();
             try {
diff --git a/core/java/android/app/backup/BackupDataOutput.java b/core/java/android/app/backup/BackupDataOutput.java
index c7586a2..5a66f34 100644
--- a/core/java/android/app/backup/BackupDataOutput.java
+++ b/core/java/android/app/backup/BackupDataOutput.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SystemApi;
 import android.os.ParcelFileDescriptor;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 
@@ -62,7 +63,10 @@
  * @see BackupAgent
  */
 public class BackupDataOutput {
-    final long mQuota;
+
+    private final long mQuota;
+    private final int mTransportFlags;
+
     long mBackupWriter;
 
     /**
@@ -71,14 +75,20 @@
      * @hide */
     @SystemApi
     public BackupDataOutput(FileDescriptor fd) {
-        this(fd, -1);
+        this(fd, /*quota=*/ -1, /*transportFlags=*/ 0);
     }
 
     /** @hide */
     @SystemApi
     public BackupDataOutput(FileDescriptor fd, long quota) {
+        this(fd, quota, /*transportFlags=*/ 0);
+    }
+
+    /** @hide */
+    public BackupDataOutput(FileDescriptor fd, long quota, int transportFlags) {
         if (fd == null) throw new NullPointerException();
         mQuota = quota;
+        mTransportFlags = transportFlags;
         mBackupWriter = ctor(fd);
         if (mBackupWriter == 0) {
             throw new RuntimeException("Native initialization failed with fd=" + fd);
@@ -96,6 +106,16 @@
     }
 
     /**
+     * Returns flags with additional information about the backup transport. For supported flags see
+     * {@link android.app.backup.BackupAgent}
+     *
+     * @see FullBackupDataOutput#getTransportFlags()
+     */
+    public int getTransportFlags() {
+        return mTransportFlags;
+    }
+
+    /**
      * Mark the beginning of one record in the backup data stream. This must be called before
      * {@link #writeEntityData}.
      * @param key A string key that uniquely identifies the data record within the application.
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 12f4483..6ec0969 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -252,6 +252,8 @@
     }
 
     /**
+     * @deprecated Since Android P app can no longer request restoring of its backup.
+     *
      * Restore the calling application from backup.  The data will be restored from the
      * current backup dataset if the application has stored data there, or from
      * the dataset used during the last full device setup operation if the current
@@ -269,6 +271,7 @@
      *
      * @return Zero on success; nonzero on error.
      */
+    @Deprecated
     public int requestRestore(RestoreObserver observer) {
         return requestRestore(observer, null);
     }
@@ -276,6 +279,8 @@
     // system APIs start here
 
     /**
+     * @deprecated Since Android P app can no longer request restoring of its backup.
+     *
      * Restore the calling application from backup.  The data will be restored from the
      * current backup dataset if the application has stored data there, or from
      * the dataset used during the last full device setup operation if the current
@@ -298,28 +303,12 @@
      *
      * @hide
      */
+    @Deprecated
     @SystemApi
     public int requestRestore(RestoreObserver observer, BackupManagerMonitor monitor) {
-        int result = -1;
-        checkServiceBinder();
-        if (sService != null) {
-            RestoreSession session = null;
-            try {
-                IRestoreSession binder = sService.beginRestoreSession(mContext.getPackageName(),
-                    null);
-                if (binder != null) {
-                    session = new RestoreSession(mContext, binder);
-                    result = session.restorePackage(mContext.getPackageName(), observer, monitor);
-                }
-            } catch (RemoteException e) {
-                Log.e(TAG, "restoreSelf() unable to contact service");
-            } finally {
-                if (session != null) {
-                    session.endRestoreSession();
-                }
-            }
-        }
-        return result;
+        Log.w(TAG, "requestRestore(): Since Android P app can no longer request restoring"
+                + " of its backup.");
+        return -1;
     }
 
     /**
diff --git a/core/java/android/app/backup/BackupManagerMonitor.java b/core/java/android/app/backup/BackupManagerMonitor.java
index a91aded..07e7688a 100644
--- a/core/java/android/app/backup/BackupManagerMonitor.java
+++ b/core/java/android/app/backup/BackupManagerMonitor.java
@@ -174,7 +174,6 @@
 
     /**
      * The transport returned {@link BackupTransport#TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED}.
-     * @hide
      */
     public static final int LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = 51;
 
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 266f58d..f456395 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -60,8 +60,6 @@
      *
      * <p>This is only valid when backup manager called {@link
      * #performBackup(PackageInfo, ParcelFileDescriptor, int)} with {@link #FLAG_INCREMENTAL}.
-     *
-     * @hide
      */
     public static final int TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED = -1006;
 
@@ -73,7 +71,7 @@
      * For key value backup, indicates that the backup data is a diff from a previous backup. The
      * transport must apply this diff to an existing backup to build the new backup set.
      *
-     * @hide
+     * @see #performBackup(PackageInfo, ParcelFileDescriptor, int)
      */
     public static final int FLAG_INCREMENTAL = 1 << 1;
 
@@ -81,7 +79,7 @@
      * For key value backup, indicates that the backup data is a complete set, not a diff from a
      * previous backup. The transport should clear any previous backup when storing this backup.
      *
-     * @hide
+     * @see #performBackup(PackageInfo, ParcelFileDescriptor, int)
      */
     public static final int FLAG_NON_INCREMENTAL = 1 << 2;
 
@@ -609,6 +607,15 @@
     }
 
     /**
+     * Returns flags with additional information about the transport, which is accessible to the
+     * {@link android.app.backup.BackupAgent}. This allows the agent to decide what to do based on
+     * properties of the transport.
+     */
+    public int getTransportFlags() {
+        return 0;
+    }
+
+    /**
      * Bridge between the actual IBackupTransport implementation and the stable API.  If the
      * binder interface needs to change, we use this layer to translate so that we can
      * (if appropriate) decouple those framework-side changes from the BackupTransport
@@ -740,6 +747,11 @@
         }
 
         @Override
+        public int getTransportFlags() {
+            return BackupTransport.this.getTransportFlags();
+        }
+
+        @Override
         public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
             return BackupTransport.this.getNextFullRestoreDataChunk(socket);
         }
diff --git a/core/java/android/app/backup/FullBackupDataOutput.java b/core/java/android/app/backup/FullBackupDataOutput.java
index 5deedd0..18f4283 100644
--- a/core/java/android/app/backup/FullBackupDataOutput.java
+++ b/core/java/android/app/backup/FullBackupDataOutput.java
@@ -11,6 +11,7 @@
     // Currently a name-scoping shim around BackupDataOutput
     private final BackupDataOutput mData;
     private final long mQuota;
+    private final int mTransportFlags;
     private long mSize;
 
     /**
@@ -23,22 +24,49 @@
         return mQuota;
     }
 
+    /**
+     * Returns flags with additional information about the backup transport. For supported flags see
+     * {@link android.app.backup.BackupAgent}
+     *
+     * @see BackupDataOutput#getTransportFlags()
+     */
+    public int getTransportFlags() {
+        return mTransportFlags;
+    }
+
     /** @hide - used only in measure operation */
     public FullBackupDataOutput(long quota) {
         mData = null;
         mQuota = quota;
         mSize = 0;
+        mTransportFlags = 0;
+    }
+
+    /** @hide - used only in measure operation */
+    public FullBackupDataOutput(long quota, int transportFlags) {
+        mData = null;
+        mQuota = quota;
+        mSize = 0;
+        mTransportFlags = transportFlags;
     }
 
     /** @hide */
     public FullBackupDataOutput(ParcelFileDescriptor fd, long quota) {
-        mData = new BackupDataOutput(fd.getFileDescriptor(), quota);
+        mData = new BackupDataOutput(fd.getFileDescriptor(), quota, 0);
         mQuota = quota;
+        mTransportFlags = 0;
+    }
+
+    /** @hide */
+    public FullBackupDataOutput(ParcelFileDescriptor fd, long quota, int transportFlags) {
+        mData = new BackupDataOutput(fd.getFileDescriptor(), quota, transportFlags);
+        mQuota = quota;
+        mTransportFlags = transportFlags;
     }
 
     /** @hide - used only internally to the backup manager service's stream construction */
     public FullBackupDataOutput(ParcelFileDescriptor fd) {
-        this(fd, -1);
+        this(fd, /*quota=*/ -1, /*transportFlags=*/ 0);
     }
 
     /** @hide */
diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java
index 35a21a4..b255a43 100644
--- a/core/java/android/bluetooth/BluetoothA2dp.java
+++ b/core/java/android/bluetooth/BluetoothA2dp.java
@@ -300,11 +300,7 @@
     }
 
     /**
-     * Initiate connection to a profile of the remote bluetooth device.
-     *
-     * <p> Currently, the system supports only 1 connection to the
-     * A2DP profile. The API will automatically disconnect connected
-     * devices before connecting.
+     * Initiate connection to a profile of the remote Bluetooth device.
      *
      * <p> This API returns false in scenarios like the profile on the
      * device is already connected or Bluetooth is not turned on.
@@ -699,15 +695,17 @@
     /**
      * Gets the current codec status (configuration and capability).
      *
+     * @param device the remote Bluetooth device. If null, use the current
+     * active A2DP Bluetooth device.
      * @return the current codec status
      * @hide
      */
-    public BluetoothCodecStatus getCodecStatus() {
-        if (DBG) Log.d(TAG, "getCodecStatus");
+    public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
         try {
             mServiceLock.readLock().lock();
             if (mService != null && isEnabled()) {
-                return mService.getCodecStatus();
+                return mService.getCodecStatus(device);
             }
             if (mService == null) {
                 Log.w(TAG, "Proxy not attached to service");
@@ -724,15 +722,18 @@
     /**
      * Sets the codec configuration preference.
      *
+     * @param device the remote Bluetooth device. If null, use the current
+     * active A2DP Bluetooth device.
      * @param codecConfig the codec configuration preference
      * @hide
      */
-    public void setCodecConfigPreference(BluetoothCodecConfig codecConfig) {
-        if (DBG) Log.d(TAG, "setCodecConfigPreference");
+    public void setCodecConfigPreference(BluetoothDevice device,
+                                         BluetoothCodecConfig codecConfig) {
+        if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")");
         try {
             mServiceLock.readLock().lock();
             if (mService != null && isEnabled()) {
-                mService.setCodecConfigPreference(codecConfig);
+                mService.setCodecConfigPreference(device, codecConfig);
             }
             if (mService == null) Log.w(TAG, "Proxy not attached to service");
             return;
@@ -747,36 +748,42 @@
     /**
      * Enables the optional codecs.
      *
+     * @param device the remote Bluetooth device. If null, use the currect
+     * active A2DP Bluetooth device.
      * @hide
      */
-    public void enableOptionalCodecs() {
-        if (DBG) Log.d(TAG, "enableOptionalCodecs");
-        enableDisableOptionalCodecs(true);
+    public void enableOptionalCodecs(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")");
+        enableDisableOptionalCodecs(device, true);
     }
 
     /**
      * Disables the optional codecs.
      *
+     * @param device the remote Bluetooth device. If null, use the currect
+     * active A2DP Bluetooth device.
      * @hide
      */
-    public void disableOptionalCodecs() {
-        if (DBG) Log.d(TAG, "disableOptionalCodecs");
-        enableDisableOptionalCodecs(false);
+    public void disableOptionalCodecs(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")");
+        enableDisableOptionalCodecs(device, false);
     }
 
     /**
      * Enables or disables the optional codecs.
      *
+     * @param device the remote Bluetooth device. If null, use the currect
+     * active A2DP Bluetooth device.
      * @param enable if true, enable the optional codecs, other disable them
      */
-    private void enableDisableOptionalCodecs(boolean enable) {
+    private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
         try {
             mServiceLock.readLock().lock();
             if (mService != null && isEnabled()) {
                 if (enable) {
-                    mService.enableOptionalCodecs();
+                    mService.enableOptionalCodecs(device);
                 } else {
-                    mService.disableOptionalCodecs();
+                    mService.disableOptionalCodecs(device);
                 }
             }
             if (mService == null) Log.w(TAG, "Proxy not attached to service");
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index f69aab01..5177e10 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -32,6 +32,7 @@
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
+import android.app.ActivityManager;
 import android.app.IApplicationThread;
 import android.app.IServiceConnection;
 import android.app.VrManager;
@@ -1834,13 +1835,17 @@
      * See {@link android.content.Context#startActivity(Intent, Bundle)}
      * Context.startActivity(Intent, Bundle)} for more details.
      *
+     * @return The corresponding flag {@link ActivityManager#START_CANCELED},
+     *         {@link ActivityManager#START_SUCCESS} etc. indicating whether the launch was
+     *         successful.
+     *
      * @throws ActivityNotFoundException &nbsp;
      *
      * @see #startActivities(Intent[])
      * @see PackageManager#resolveActivity
      */
     @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
-    public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+    public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
         throw new RuntimeException("Not implemented. Must override in a subclass.");
     }
 
@@ -4121,7 +4126,7 @@
     public static final String STATS_COMPANION_SERVICE = "statscompanion";
 
     /**
-     * Use with {@link #getSystemService(String)} to retrieve an {@link android.stats.StatsManager}.
+     * Use with {@link #getSystemService(String)} to retrieve an {@link android.app.StatsManager}.
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 67de4fe..8c1293e 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -418,8 +418,8 @@
 
     /** @hide */
     @Override
-    public void startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
-        mBase.startActivitiesAsUser(intents, options, userHandle);
+    public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
+        return mBase.startActivitiesAsUser(intents, options, userHandle);
     }
 
     @Override
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 13ec4fd..09a46b8 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -16,14 +16,10 @@
 
 package android.content.pm;
 
-import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * Overall information about the contents of a package.  This corresponds
  * to all of the information collected from AndroidManifest.xml.
@@ -370,28 +366,9 @@
     public int overlayPriority;
 
     /**
-     * Flag for use with {@link #mOverlayFlags}. Marks the overlay as static, meaning it cannot
-     * be enabled/disabled at runtime.
+     * Whether the overlay is static, meaning it cannot be enabled/disabled at runtime.
      */
-    static final int FLAG_OVERLAY_STATIC = 1 << 1;
-
-    /**
-     * Flag for use with {@link #mOverlayFlags}. Marks the overlay as trusted (not 3rd party).
-     */
-    static final int FLAG_OVERLAY_TRUSTED = 1 << 2;
-
-    @IntDef(flag = true, prefix = "FLAG_OVERLAY_", value = {
-            FLAG_OVERLAY_STATIC,
-            FLAG_OVERLAY_TRUSTED
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @interface OverlayFlags {}
-
-    /**
-     * Modifiers that affect the state of this overlay. See {@link #FLAG_OVERLAY_STATIC},
-     * {@link #FLAG_OVERLAY_TRUSTED}.
-     */
-    @OverlayFlags int mOverlayFlags;
+    boolean mOverlayIsStatic;
 
     /**
      * The user-visible SDK version (ex. 26) of the framework against which the application claims
@@ -424,7 +401,7 @@
      * @hide
      */
     public boolean isOverlayPackage() {
-        return overlayTarget != null && (mOverlayFlags & FLAG_OVERLAY_TRUSTED) != 0;
+        return overlayTarget != null;
     }
 
     /**
@@ -433,7 +410,7 @@
      * @hide
      */
     public boolean isStaticOverlayPackage() {
-        return overlayTarget != null && (mOverlayFlags & FLAG_OVERLAY_STATIC) != 0;
+        return overlayTarget != null && mOverlayIsStatic;
     }
 
     @Override
@@ -488,7 +465,7 @@
         dest.writeString(requiredAccountType);
         dest.writeString(overlayTarget);
         dest.writeInt(overlayPriority);
-        dest.writeInt(mOverlayFlags);
+        dest.writeBoolean(mOverlayIsStatic);
         dest.writeInt(compileSdkVersion);
         dest.writeString(compileSdkVersionCodename);
     }
@@ -543,7 +520,7 @@
         requiredAccountType = source.readString();
         overlayTarget = source.readString();
         overlayPriority = source.readInt();
-        mOverlayFlags = source.readInt();
+        mOverlayIsStatic = source.readBoolean();
         compileSdkVersion = source.readInt();
         compileSdkVersionCodename = source.readString();
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 67c9584..3709a90 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1766,6 +1766,16 @@
             "android.hardware.camera.capability.raw";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: At least one
+     * of the cameras on the device supports the
+     * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING
+     * MOTION_TRACKING} capability level.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_CAMERA_AR =
+            "android.hardware.camera.ar";
+
+    /**
      * Feature for {@link #getSystemAvailableFeatures} and
      * {@link #hasSystemFeature}: The device is capable of communicating with
      * consumer IR devices.
@@ -2594,6 +2604,14 @@
     public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
 
     /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * The device has a StrongBox hardware-backed Keystore.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_STRONGBOX_KEYSTORE =
+            "android.hardware.strongbox_keystore";
+
+    /**
      * Action to external storage service to clean out removed apps.
      * @hide
      */
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5b5ccf5..c705ef5 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;
@@ -679,15 +680,7 @@
         pi.requiredAccountType = p.mRequiredAccountType;
         pi.overlayTarget = p.mOverlayTarget;
         pi.overlayPriority = p.mOverlayPriority;
-
-        if (p.mIsStaticOverlay) {
-            pi.mOverlayFlags |= PackageInfo.FLAG_OVERLAY_STATIC;
-        }
-
-        if (p.mTrustedOverlay) {
-            pi.mOverlayFlags |= PackageInfo.FLAG_OVERLAY_TRUSTED;
-        }
-
+        pi.mOverlayIsStatic = p.mOverlayIsStatic;
         pi.compileSdkVersion = p.mCompileSdkVersion;
         pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename;
         pi.firstInstallTime = firstInstallTime;
@@ -1318,24 +1311,6 @@
         }
     }
 
-    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();
@@ -1351,13 +1326,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);
@@ -1392,15 +1369,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);
@@ -1602,21 +1582,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) {
@@ -1642,7 +1620,7 @@
                     "Failed to parse " + apkPath, e);
         } finally {
             IoUtils.closeQuietly(parser);
-            IoUtils.closeQuietly(assets);
+            // TODO(b/72056911): Implement and call close() on ApkAssets.
         }
     }
 
@@ -2085,7 +2063,7 @@
                 pkg.mOverlayPriority = sa.getInt(
                         com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority,
                         0);
-                pkg.mIsStaticOverlay = sa.getBoolean(
+                pkg.mOverlayIsStatic = sa.getBoolean(
                         com.android.internal.R.styleable.AndroidManifestResourceOverlay_isStatic,
                         false);
                 final String propName = sa.getString(
@@ -6094,8 +6072,7 @@
 
         public String mOverlayTarget;
         public int mOverlayPriority;
-        public boolean mIsStaticOverlay;
-        public boolean mTrustedOverlay;
+        public boolean mOverlayIsStatic;
 
         public int mCompileSdkVersion;
         public String mCompileSdkVersionCodename;
@@ -6625,8 +6602,7 @@
             mRequiredAccountType = dest.readString();
             mOverlayTarget = dest.readString();
             mOverlayPriority = dest.readInt();
-            mIsStaticOverlay = (dest.readInt() == 1);
-            mTrustedOverlay = (dest.readInt() == 1);
+            mOverlayIsStatic = (dest.readInt() == 1);
             mCompileSdkVersion = dest.readInt();
             mCompileSdkVersionCodename = dest.readString();
             mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
@@ -6749,8 +6725,7 @@
             dest.writeString(mRequiredAccountType);
             dest.writeString(mOverlayTarget);
             dest.writeInt(mOverlayPriority);
-            dest.writeInt(mIsStaticOverlay ? 1 : 0);
-            dest.writeInt(mTrustedOverlay ? 1 : 0);
+            dest.writeInt(mOverlayIsStatic ? 1 : 0);
             dest.writeInt(mCompileSdkVersion);
             dest.writeString(mCompileSdkVersionCodename);
             dest.writeArraySet(mUpgradeKeySets);
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..abaf7014 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,18 @@
 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 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 +52,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 +87,277 @@
      */
     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.
+     * @hide
+     */
+    private static void createSystemAssetsInZygoteLocked() {
+        if (sSystem != null) {
+            return;
         }
-    }
-    
-    private AssetManager(boolean isSystem) {
-        if (DEBUG_REFS) {
-            synchronized (this) {
-                mNumRefs = 0;
-                incRefsLocked(this.hashCode());
+
+        // 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*/));
+
+            // Load all static RROs.
+            try (FileInputStream fis = new FileInputStream(
+                    "/data/resource-cache/overlays.list");
+                 BufferedReader br = new BufferedReader(new InputStreamReader(fis))) {
+                // Acquire a lock so that any idmap scanning doesn't impact the current set.
+                try (FileLock flock = fis.getChannel().lock(0, Long.MAX_VALUE,
+                        true /*shared*/)) {
+                    for (String line; (line = br.readLine()) != null; ) {
+                        String idmapPath = line.split(" ")[1];
+                        apkAssets.add(
+                                ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
+                    }
+                }
             }
+
+            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);
         }
-        init(true);
-        if (localLOGV) Log.v(TAG, "New asset manager: " + this);
     }
 
     /**
      * 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 +372,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 +388,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 +551,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 +642,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 +652,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 +700,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 +822,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 +833,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 +1104,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 +1119,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 +1177,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..2a4b278 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -168,7 +168,6 @@
         mDisplayAdjustments = displayAdjustments;
         mConfiguration.setToDefaults();
         updateConfiguration(config, metrics, displayAdjustments.getCompatibilityInfo());
-        mAssets.ensureStringBlocks();
     }
 
     public DisplayAdjustments getDisplayAdjustments() {
@@ -1274,8 +1273,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 +1282,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 +1305,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 +1325,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 +1345,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 +1381,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/hardware/camera2/TotalCaptureResult.java b/core/java/android/hardware/camera2/TotalCaptureResult.java
index 657745c..bae2d04 100644
--- a/core/java/android/hardware/camera2/TotalCaptureResult.java
+++ b/core/java/android/hardware/camera2/TotalCaptureResult.java
@@ -17,11 +17,14 @@
 package android.hardware.camera2;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 
 /**
@@ -44,6 +47,12 @@
  * as {@link CaptureRequest#STATISTICS_FACE_DETECT_MODE}). Refer to each key documentation on
  * a case-by-case basis.</p>
  *
+ * <p>For a logical multi-camera device, if the CaptureRequest contains a surface for an underlying
+ * physical camera, the corresponding {@link TotalCaptureResult} object will include the metadata
+ * for that physical camera. And its keys and values can be accessed by
+ * {@link #getPhysicalCameraKey}. If all requested surfaces are for the logical camera, no
+ * metadata for physical camera will be included.</p>
+ *
  * <p>{@link TotalCaptureResult} objects are immutable.</p>
  *
  * @see CameraDevice.CaptureCallback#onCaptureCompleted
@@ -52,6 +61,8 @@
 
     private final List<CaptureResult> mPartialResults;
     private final int mSessionId;
+    // The map between physical camera id and capture result
+    private final HashMap<String, CameraMetadataNative> mPhysicalCaptureResults;
 
     /**
      * Takes ownership of the passed-in camera metadata and the partial results
@@ -60,7 +71,8 @@
      * @hide
      */
     public TotalCaptureResult(CameraMetadataNative results, CaptureRequest parent,
-            CaptureResultExtras extras, List<CaptureResult> partials, int sessionId) {
+            CaptureResultExtras extras, List<CaptureResult> partials, int sessionId,
+            PhysicalCaptureResultInfo physicalResults[]) {
         super(results, parent, extras);
 
         if (partials == null) {
@@ -70,6 +82,12 @@
         }
 
         mSessionId = sessionId;
+
+        mPhysicalCaptureResults = new HashMap<String, CameraMetadataNative>();
+        for (PhysicalCaptureResultInfo onePhysicalResult : physicalResults) {
+            mPhysicalCaptureResults.put(onePhysicalResult.getCameraId(),
+                    onePhysicalResult.getCameraMetadata());
+        }
     }
 
     /**
@@ -83,6 +101,7 @@
 
         mPartialResults = new ArrayList<>();
         mSessionId = CameraCaptureSession.SESSION_ID_NONE;
+        mPhysicalCaptureResults = new HashMap<String, CameraMetadataNative>();
     }
 
     /**
@@ -111,4 +130,38 @@
     public int getSessionId() {
         return mSessionId;
     }
+
+    /**
+     * Get a capture result field value for a particular physical camera id.
+     *
+     * <p>The field definitions can be found in {@link CaptureResult}.</p>
+     *
+     * <p>This function can be called for logical camera devices, which are devices that have
+     * REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA capability and calls to {@link
+     * CameraCharacteristics#getPhysicalCameraIds} return a non-empty list of physical devices that
+     * are backing the logical camera. The camera id included in physicalCameraId argument
+     * selects an individual physical device, and returns its specific capture result field.</p>
+     *
+     * <p>This function should only be called if one or more streams from the underlying
+     * 'physicalCameraId' was requested by the corresponding capture request.</p>
+     *
+     * @throws IllegalArgumentException if the key was not valid, or the physicalCameraId is not
+     * applicable to the current camera, or a stream from 'physicalCameraId' is not requested by the
+     * corresponding capture request.
+     *
+     * @param key The result field to read.
+     * @param physicalCameraId The physical camera the result originates from.
+     *
+     * @return The value of that key, or {@code null} if the field is not set.
+     */
+    @Nullable
+    public <T> T getPhysicalCameraKey(Key<T> key, @NonNull String physicalCameraId) {
+        if (!mPhysicalCaptureResults.containsKey(physicalCameraId)) {
+            throw new IllegalArgumentException(
+                    "No TotalCaptureResult exists for physical camera " + physicalCameraId);
+        }
+
+        CameraMetadataNative physicalMetadata = mPhysicalCaptureResults.get(physicalCameraId);
+        return physicalMetadata.get(key);
+    }
 }
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index cab9d70..511fa43 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -1001,19 +1001,17 @@
                     throw new IllegalArgumentException("Null Surface targets are not allowed");
                 }
 
-                if (!request.isReprocess()) {
-                    continue;
-                }
                 for (int i = 0; i < mConfiguredOutputs.size(); i++) {
                     OutputConfiguration configuration = mConfiguredOutputs.valueAt(i);
                     if (configuration.isForPhysicalCamera()
                             && configuration.getSurfaces().contains(surface)) {
-                        throw new IllegalArgumentException(
-                                "Reprocess request on physical stream is not allowed");
+                        if (request.isReprocess()) {
+                            throw new IllegalArgumentException(
+                                    "Reprocess request on physical stream is not allowed");
+                        }
                     }
                 }
             }
-
         }
 
         synchronized(mInterfaceLock) {
@@ -1966,7 +1964,8 @@
 
         @Override
         public void onResultReceived(CameraMetadataNative result,
-                CaptureResultExtras resultExtras) throws RemoteException {
+                CaptureResultExtras resultExtras, PhysicalCaptureResultInfo physicalResults[])
+                throws RemoteException {
 
             int requestId = resultExtras.getRequestId();
             long frameNumber = resultExtras.getFrameNumber();
@@ -2073,7 +2072,8 @@
                             request.get(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE);
                     final int subsequenceId = resultExtras.getSubsequenceId();
                     final TotalCaptureResult resultAsCapture = new TotalCaptureResult(result,
-                            request, resultExtras, partialResults, holder.getSessionId());
+                            request, resultExtras, partialResults, holder.getSessionId(),
+                            physicalResults);
                     // Final capture result
                     resultDispatch = new Runnable() {
                         @Override
@@ -2088,9 +2088,11 @@
                                                 NANO_PER_SECOND/fpsRange.getUpper());
                                         CameraMetadataNative resultLocal =
                                                 new CameraMetadataNative(resultCopy);
+                                        // No logical multi-camera support for batched output mode.
                                         TotalCaptureResult resultInBatch = new TotalCaptureResult(
                                             resultLocal, holder.getRequest(i), resultExtras,
-                                            partialResults, holder.getSessionId());
+                                            partialResults, holder.getSessionId(),
+                                            new PhysicalCaptureResultInfo[0]);
 
                                         holder.getCallback().onCaptureCompleted(
                                             CameraDeviceImpl.this,
diff --git a/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java b/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.java
new file mode 100644
index 0000000..30eaf13
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/PhysicalCaptureResultInfo.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 android.hardware.camera2.impl;
+
+import android.hardware.camera2.impl.CameraMetadataNative;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * @hide
+ */
+public class PhysicalCaptureResultInfo implements Parcelable {
+    private String cameraId;
+    private CameraMetadataNative cameraMetadata;
+
+    public static final Parcelable.Creator<PhysicalCaptureResultInfo> CREATOR =
+            new Parcelable.Creator<PhysicalCaptureResultInfo>() {
+        @Override
+        public PhysicalCaptureResultInfo createFromParcel(Parcel in) {
+            return new PhysicalCaptureResultInfo(in);
+        }
+
+        @Override
+        public PhysicalCaptureResultInfo[] newArray(int size) {
+            return new PhysicalCaptureResultInfo[size];
+        }
+    };
+
+    private PhysicalCaptureResultInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public PhysicalCaptureResultInfo(String cameraId, CameraMetadataNative cameraMetadata) {
+        this.cameraId = cameraId;
+        this.cameraMetadata = cameraMetadata;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(cameraId);
+        cameraMetadata.writeToParcel(dest, flags);
+    }
+
+    public void readFromParcel(Parcel in) {
+        cameraId = in.readString();
+        cameraMetadata = new CameraMetadataNative();
+        cameraMetadata.readFromParcel(in);
+    }
+
+    public String getCameraId() {
+        return cameraId;
+    }
+
+    public CameraMetadataNative getCameraMetadata() {
+        return cameraMetadata;
+    }
+}
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index eccab75..bc7b126 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -26,6 +26,7 @@
 import android.hardware.camera2.ICameraDeviceUser;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.utils.SubmitInfo;
 import android.os.ConditionVariable;
@@ -249,7 +250,8 @@
 
         @Override
         public void onResultReceived(final CameraMetadataNative result,
-                final CaptureResultExtras resultExtras) {
+                final CaptureResultExtras resultExtras,
+                PhysicalCaptureResultInfo physicalResults[]) {
             Object[] resultArray = new Object[] { result, resultExtras };
             Message msg = getHandler().obtainMessage(RESULT_RECEIVED,
                     /*obj*/ resultArray);
@@ -320,7 +322,8 @@
                             Object[] resultArray = (Object[]) msg.obj;
                             CameraMetadataNative result = (CameraMetadataNative) resultArray[0];
                             CaptureResultExtras resultExtras = (CaptureResultExtras) resultArray[1];
-                            mCallbacks.onResultReceived(result, resultExtras);
+                            mCallbacks.onResultReceived(result, resultExtras,
+                                    new PhysicalCaptureResultInfo[0]);
                             break;
                         }
                         case PREPARED: {
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index cb59fd1..e7f2134 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -23,6 +23,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.impl.CameraDeviceImpl;
 import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.utils.ArrayUtils;
@@ -253,7 +254,8 @@
                                 holder.getRequestId());
                     }
                     try {
-                        mDeviceCallbacks.onResultReceived(result, extras);
+                        mDeviceCallbacks.onResultReceived(result, extras,
+                                new PhysicalCaptureResultInfo[0]);
                     } catch (RemoteException e) {
                         throw new IllegalStateException(
                                 "Received remote exception during onCameraError callback: ", e);
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 37e2c4f..9ccdbe2 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -17,11 +17,14 @@
 
 import static android.net.IpSecManager.INVALID_RESOURCE_ID;
 
+import static com.android.internal.util.Preconditions.checkNotNull;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -128,13 +131,6 @@
                 int status = result.status;
                 checkResultStatus(status);
                 mResourceId = result.resourceId;
-
-                /* Keepalive will silently fail if not needed by the config; but, if needed and
-                 * it fails to start, we need to bail because a transform will not be reliable
-                 * to use if keepalive is expected to offload and fails.
-                 */
-                // FIXME: if keepalive fails, we need to fail spectacularly
-                startKeepalive(mContext);
                 Log.d(TAG, "Added Transform with Id " + mResourceId);
                 mCloseGuard.open("build");
             } catch (RemoteException e) {
@@ -164,13 +160,9 @@
             return;
         }
         try {
-            /* Order matters here because the keepalive is best-effort but could fail in some
-             * horrible way to be removed if the wifi (or cell) subsystem has crashed, and we
-             * still want to clear out the transform.
-             */
             IIpSecService svc = getIpSecService();
             svc.deleteTransform(mResourceId);
-            stopKeepalive();
+            stopNattKeepalive();
         } catch (RemoteException e) {
             throw e.rethrowAsRuntimeException();
         } finally {
@@ -198,42 +190,35 @@
     private final Context mContext;
     private final CloseGuard mCloseGuard = CloseGuard.get();
     private ConnectivityManager.PacketKeepalive mKeepalive;
-    private int mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
-    private Object mKeepaliveSyncLock = new Object();
-    private ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback =
+    private Handler mCallbackHandler;
+    private final ConnectivityManager.PacketKeepaliveCallback mKeepaliveCallback =
             new ConnectivityManager.PacketKeepaliveCallback() {
 
                 @Override
                 public void onStarted() {
-                    synchronized (mKeepaliveSyncLock) {
-                        mKeepaliveStatus = ConnectivityManager.PacketKeepalive.SUCCESS;
-                        mKeepaliveSyncLock.notifyAll();
+                    synchronized (this) {
+                        mCallbackHandler.post(() -> mUserKeepaliveCallback.onStarted());
                     }
                 }
 
                 @Override
                 public void onStopped() {
-                    synchronized (mKeepaliveSyncLock) {
-                        mKeepaliveStatus = ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
-                        mKeepaliveSyncLock.notifyAll();
+                    synchronized (this) {
+                        mKeepalive = null;
+                        mCallbackHandler.post(() -> mUserKeepaliveCallback.onStopped());
                     }
                 }
 
                 @Override
                 public void onError(int error) {
-                    synchronized (mKeepaliveSyncLock) {
-                        mKeepaliveStatus = error;
-                        mKeepaliveSyncLock.notifyAll();
+                    synchronized (this) {
+                        mKeepalive = null;
+                        mCallbackHandler.post(() -> mUserKeepaliveCallback.onError(error));
                     }
                 }
             };
 
-    /* Package */
-    void startKeepalive(Context c) {
-        if (mConfig.getNattKeepaliveInterval() != 0) {
-            Log.wtf(TAG, "Keepalive not yet supported.");
-        }
-    }
+    private NattKeepaliveCallback mUserKeepaliveCallback;
 
     /** @hide */
     @VisibleForTesting
@@ -241,9 +226,93 @@
         return mResourceId;
     }
 
-    /* Package */
-    void stopKeepalive() {
-        return;
+    /**
+     * A callback class to provide status information regarding a NAT-T keepalive session
+     *
+     * <p>Use this callback to receive status information regarding a NAT-T keepalive session
+     * by registering it when calling {@link #startNattKeepalive}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static class NattKeepaliveCallback {
+        /** The specified {@code Network} is not connected. */
+        public static final int ERROR_INVALID_NETWORK = 1;
+        /** The hardware does not support this request. */
+        public static final int ERROR_HARDWARE_UNSUPPORTED = 2;
+        /** The hardware returned an error. */
+        public static final int ERROR_HARDWARE_ERROR = 3;
+
+        /** The requested keepalive was successfully started. */
+        public void onStarted() {}
+        /** The keepalive was successfully stopped. */
+        public void onStopped() {}
+        /** An error occurred. */
+        public void onError(int error) {}
+    }
+
+    /**
+     * Start a NAT-T keepalive session for the current transform.
+     *
+     * For a transform that is using UDP encapsulated IPv4, NAT-T offloading provides
+     * a power efficient mechanism of sending NAT-T packets at a specified interval.
+     *
+     * @param userCallback a {@link #NattKeepaliveCallback} to receive asynchronous status
+     *      information about the requested NAT-T keepalive session.
+     * @param intervalSeconds the interval between NAT-T keepalives being sent. The
+     *      the allowed range is between 20 and 3600 seconds.
+     * @param handler a handler on which to post callbacks when received.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void startNattKeepalive(@NonNull NattKeepaliveCallback userCallback,
+            int intervalSeconds, @NonNull Handler handler) throws IOException {
+        checkNotNull(userCallback);
+        if (intervalSeconds < 20 || intervalSeconds > 3600) {
+            throw new IllegalArgumentException("Invalid NAT-T keepalive interval");
+        }
+        checkNotNull(handler);
+        if (mResourceId == INVALID_RESOURCE_ID) {
+            throw new IllegalStateException(
+                    "Packet keepalive cannot be started for an inactive transform");
+        }
+
+        synchronized (mKeepaliveCallback) {
+            if (mKeepaliveCallback != null) {
+                throw new IllegalStateException("Keepalive already active");
+            }
+
+            mUserKeepaliveCallback = userCallback;
+            ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
+                    Context.CONNECTIVITY_SERVICE);
+            mKeepalive = cm.startNattKeepalive(
+                    mConfig.getNetwork(), intervalSeconds, mKeepaliveCallback,
+                    NetworkUtils.numericToInetAddress(mConfig.getSourceAddress()),
+                    4500, // FIXME urgently, we need to get the port number from the Encap socket
+                    NetworkUtils.numericToInetAddress(mConfig.getDestinationAddress()));
+            mCallbackHandler = handler;
+        }
+    }
+
+    /**
+     * Stop an ongoing NAT-T keepalive session.
+     *
+     * Calling this API will request that an ongoing NAT-T keepalive session be terminated.
+     * If this API is not called when a Transform is closed, the underlying NAT-T session will
+     * be terminated automatically.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void stopNattKeepalive() {
+        synchronized (mKeepaliveCallback) {
+            if (mKeepalive == null) {
+                Log.e(TAG, "No active keepalive to stop");
+                return;
+            }
+            mKeepalive.stop();
+        }
     }
 
     /** This class is used to build {@link IpSecTransform} objects. */
@@ -323,26 +392,6 @@
             return this;
         }
 
-        // TODO: Decrease the minimum keepalive to maybe 10?
-        // TODO: Probably a better exception to throw for NATTKeepalive failure
-        // TODO: Specify the needed NATT keepalive permission.
-        /**
-         * Set NAT-T keepalives to be sent with a given interval.
-         *
-         * <p>This will set power-efficient keepalive packets to be sent by the system. If NAT-T
-         * keepalive is requested but cannot be activated, then creation of an {@link
-         * IpSecTransform} will fail when calling the build method.
-         *
-         * @param intervalSeconds the maximum number of seconds between keepalive packets. Must be
-         *     between 20s and 3600s.
-         * @hide
-         */
-        @SystemApi
-        public IpSecTransform.Builder setNattKeepalive(int intervalSeconds) {
-            mConfig.setNattKeepaliveInterval(intervalSeconds);
-            return this;
-        }
-
         /**
          * Build a transport mode {@link IpSecTransform}.
          *
diff --git a/core/java/android/net/KeepalivePacketData.aidl b/core/java/android/net/KeepalivePacketData.aidl
new file mode 100644
index 0000000..d456b53
--- /dev/null
+++ b/core/java/android/net/KeepalivePacketData.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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 android.net;
+
+parcelable KeepalivePacketData;
diff --git a/core/java/android/net/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java
new file mode 100644
index 0000000..08d4ff5
--- /dev/null
+++ b/core/java/android/net/KeepalivePacketData.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.system.OsConstants;
+import android.net.ConnectivityManager;
+import android.net.util.IpUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.system.OsConstants;
+import android.util.Log;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import static android.net.ConnectivityManager.PacketKeepalive.*;
+
+/**
+ * Represents the actual packets that are sent by the
+ * {@link android.net.ConnectivityManager.PacketKeepalive} API.
+ *
+ * @hide
+ */
+public class KeepalivePacketData implements Parcelable {
+    private static final String TAG = "KeepalivePacketData";
+
+    /** Source IP address */
+    public final InetAddress srcAddress;
+
+    /** Destination IP address */
+    public final InetAddress dstAddress;
+
+    /** Source port */
+    public final int srcPort;
+
+    /** Destination port */
+    public final int dstPort;
+
+    /** Packet data. A raw byte string of packet data, not including the link-layer header. */
+    private final byte[] mPacket;
+
+    private static final int IPV4_HEADER_LENGTH = 20;
+    private static final int UDP_HEADER_LENGTH = 8;
+
+    // This should only be constructed via static factory methods, such as
+    // nattKeepalivePacket
+    protected KeepalivePacketData(InetAddress srcAddress, int srcPort,
+            InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException {
+        this.srcAddress = srcAddress;
+        this.dstAddress = dstAddress;
+        this.srcPort = srcPort;
+        this.dstPort = dstPort;
+        this.mPacket = data;
+
+        // Check we have two IP addresses of the same family.
+        if (srcAddress == null || dstAddress == null || !srcAddress.getClass().getName()
+                .equals(dstAddress.getClass().getName())) {
+            Log.e(TAG, "Invalid or mismatched InetAddresses in KeepalivePacketData");
+            throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+        }
+
+        // Check the ports.
+        if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) {
+            Log.e(TAG, "Invalid ports in KeepalivePacketData");
+            throw new InvalidPacketException(ERROR_INVALID_PORT);
+        }
+    }
+
+    public static class InvalidPacketException extends Exception {
+        public final int error;
+        public InvalidPacketException(int error) {
+            this.error = error;
+        }
+    }
+
+    public byte[] getPacket() {
+        return mPacket.clone();
+    }
+
+    public static KeepalivePacketData nattKeepalivePacket(
+            InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort)
+            throws InvalidPacketException {
+
+        // FIXME: remove this and actually support IPv6 keepalives
+        if (srcAddress instanceof Inet6Address && dstAddress instanceof Inet6Address) {
+            // Optimistically returning an IPv6 Keepalive Packet with no data,
+            // which currently only works on cellular
+            return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, new byte[0]);
+        }
+
+        if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) {
+            throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+        }
+
+        if (dstPort != NATT_PORT) {
+            throw new InvalidPacketException(ERROR_INVALID_PORT);
+        }
+
+        int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1;
+        ByteBuffer buf = ByteBuffer.allocate(length);
+        buf.order(ByteOrder.BIG_ENDIAN);
+        buf.putShort((short) 0x4500);             // IP version and TOS
+        buf.putShort((short) length);
+        buf.putInt(0);                            // ID, flags, offset
+        buf.put((byte) 64);                       // TTL
+        buf.put((byte) OsConstants.IPPROTO_UDP);
+        int ipChecksumOffset = buf.position();
+        buf.putShort((short) 0);                  // IP checksum
+        buf.put(srcAddress.getAddress());
+        buf.put(dstAddress.getAddress());
+        buf.putShort((short) srcPort);
+        buf.putShort((short) dstPort);
+        buf.putShort((short) (length - 20));      // UDP length
+        int udpChecksumOffset = buf.position();
+        buf.putShort((short) 0);                  // UDP checksum
+        buf.put((byte) 0xff);                     // NAT-T keepalive
+        buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
+        buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_LENGTH));
+
+        return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
+    }
+
+    /* Parcelable Implementation */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Write to parcel */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(srcAddress.getHostAddress());
+        out.writeString(dstAddress.getHostAddress());
+        out.writeInt(srcPort);
+        out.writeInt(dstPort);
+        out.writeByteArray(mPacket);
+    }
+
+    private KeepalivePacketData(Parcel in) {
+        srcAddress = NetworkUtils.numericToInetAddress(in.readString());
+        dstAddress = NetworkUtils.numericToInetAddress(in.readString());
+        srcPort = in.readInt();
+        dstPort = in.readInt();
+        mPacket = in.createByteArray();
+    }
+
+    /** Parcelable Creator */
+    public static final Parcelable.Creator<KeepalivePacketData> CREATOR =
+            new Parcelable.Creator<KeepalivePacketData>() {
+                public KeepalivePacketData createFromParcel(Parcel in) {
+                    return new KeepalivePacketData(in);
+                }
+
+                public KeepalivePacketData[] newArray(int size) {
+                    return new KeepalivePacketData[size];
+                }
+            };
+
+}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 1a4765b..8e05cfa 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -892,7 +892,7 @@
      *
      * @hide
      */
-    private Set<UidRange> mUids = null;
+    private ArraySet<UidRange> mUids = null;
 
     /**
      * Convenience method to set the UIDs this network applies to to a single UID.
@@ -1178,7 +1178,7 @@
         dest.writeInt(mLinkDownBandwidthKbps);
         dest.writeParcelable((Parcelable) mNetworkSpecifier, flags);
         dest.writeInt(mSignalStrength);
-        dest.writeArraySet(new ArraySet<>(mUids));
+        dest.writeArraySet(mUids);
     }
 
     public static final Creator<NetworkCapabilities> CREATOR =
diff --git a/services/net/java/android/net/util/IpUtils.java b/core/java/android/net/util/IpUtils.java
similarity index 100%
rename from services/net/java/android/net/util/IpUtils.java
rename to core/java/android/net/util/IpUtils.java
diff --git a/core/java/android/os/IStatsCompanionService.aidl b/core/java/android/os/IStatsCompanionService.aidl
index 1d2a408..8a27700 100644
--- a/core/java/android/os/IStatsCompanionService.aidl
+++ b/core/java/android/os/IStatsCompanionService.aidl
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.os.StatsDimensionsValue;
 import android.os.StatsLogEventWrapper;
 
 /**
@@ -55,8 +56,17 @@
     StatsLogEventWrapper[] pullData(int pullCode);
 
     /** Send a broadcast to the specified pkg and class that it should getData now. */
+    // TODO: Rename this and use a pending intent instead.
     oneway void sendBroadcast(String pkg, String cls);
 
+    /**
+     * Requests StatsCompanionService to send a broadcast using the given intentSender
+     * (which should cast to an IIntentSender), along with the other information specified.
+     */
+    oneway void sendSubscriberBroadcast(in IBinder intentSender, long configUid, long configId,
+                                        long subscriptionId, long subscriptionRuleId,
+                                        in StatsDimensionsValue dimensionsValue);
+
     /** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */
     oneway void triggerUidSnapshot();
 }
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 29812e8..679b49d 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -81,7 +81,7 @@
     /**
      * Sets a configuration with the specified config key and subscribes to updates for this
      * configuration key. Broadcasts will be sent if this configuration needs to be collected.
-     * The configuration must be a wire-encoded StatsDConfig. The caller specifies the name of the
+     * The configuration must be a wire-encoded StatsdConfig. The caller specifies the name of the
      * package and class that should receive these broadcasts.
      *
      * Returns if this configuration was correctly registered.
@@ -95,4 +95,33 @@
      * Returns if this configuration key was removed.
      */
     boolean removeConfiguration(in long configKey);
+
+    /**
+     * Set the IIntentSender (i.e. PendingIntent) to be used when broadcasting subscriber
+     * information to the given subscriberId within the given config.
+     *
+     * Suppose that the calling uid has added a config with key configKey, and that in this config
+     * it is specified that when a particular anomaly is detected, a broadcast should be sent to
+     * a BroadcastSubscriber with id subscriberId. This function links the given intentSender with
+     * that subscriberId (for that config), so that this intentSender is used to send the broadcast
+     * when the anomaly is detected.
+     *
+     * This function can only be called by the owner (uid) of the config. It must be called each
+     * time statsd starts. Later calls overwrite previous calls; only one intentSender is stored.
+     *
+     * intentSender must be convertible into an IntentSender using IntentSender(IBinder)
+     * and cannot be null.
+     *
+     * Returns true if successful.
+     */
+    boolean setBroadcastSubscriber(long configKey, long subscriberId, in IBinder intentSender);
+
+    /**
+     * Undoes setBroadcastSubscriber() for the (configKey, subscriberId) pair.
+     * Any broadcasts associated with subscriberId will henceforth not be sent.
+     * No-op if this (configKey, subsriberId) pair was not associated with an IntentSender.
+     *
+     * Returns true if successful.
+     */
+    boolean unsetBroadcastSubscriber(long configKey, long subscriberId);
 }
diff --git a/core/java/android/os/StatsDimensionsValue.aidl b/core/java/android/os/StatsDimensionsValue.aidl
new file mode 100644
index 0000000..81a14a4
--- /dev/null
+++ b/core/java/android/os/StatsDimensionsValue.aidl
@@ -0,0 +1,20 @@
+/**
+ * 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 android.os;
+
+/** @hide */
+parcelable StatsDimensionsValue cpp_header "android/os/StatsDimensionsValue.h";
\ No newline at end of file
diff --git a/core/java/android/os/StatsDimensionsValue.java b/core/java/android/os/StatsDimensionsValue.java
new file mode 100644
index 0000000..257cc52
--- /dev/null
+++ b/core/java/android/os/StatsDimensionsValue.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright 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 android.os;
+
+import android.annotation.SystemApi;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Container for statsd dimension value information, corresponding to a
+ * stats_log.proto's DimensionValue.
+ *
+ * This consists of a field (an int representing a statsd atom field)
+ * and a value (which may be one of a number of types).
+ *
+ * <p>
+ * Only a single value is held, and it is necessarily one of the following types:
+ * {@link String}, int, long, boolean, float,
+ * or tuple (i.e. {@link List} of {@code StatsDimensionsValue}).
+ *
+ * The type of value held can be retrieved using {@link #getValueType()}, which returns one of the
+ * following ints, depending on the type of value:
+ * <ul>
+ *  <li>{@link #STRING_VALUE_TYPE}</li>
+ *  <li>{@link #INT_VALUE_TYPE}</li>
+ *  <li>{@link #LONG_VALUE_TYPE}</li>
+ *  <li>{@link #BOOLEAN_VALUE_TYPE}</li>
+ *  <li>{@link #FLOAT_VALUE_TYPE}</li>
+ *  <li>{@link #TUPLE_VALUE_TYPE}</li>
+ * </ul>
+ * Alternatively, this can be determined using {@link #isValueType(int)} with one of these constants
+ * as a parameter.
+ * The value itself can be retrieved using the correct get...Value() function for its type.
+ *
+ * <p>
+ * The field is always an int, and always exists; it can be obtained using {@link #getField()}.
+ *
+ *
+ * @hide
+ */
+@SystemApi
+public final class StatsDimensionsValue implements Parcelable {
+    private static final String TAG = "StatsDimensionsValue";
+
+    // Values of the value type correspond to stats_log.proto's DimensionValue fields.
+    // Keep constants in sync with services/include/android/os/StatsDimensionsValue.h.
+    /** Indicates that this holds a String. */
+    public static final int STRING_VALUE_TYPE = 2;
+    /** Indicates that this holds an int. */
+    public static final int INT_VALUE_TYPE = 3;
+    /** Indicates that this holds a long. */
+    public static final int LONG_VALUE_TYPE = 4;
+    /** Indicates that this holds a boolean. */
+    public static final int BOOLEAN_VALUE_TYPE = 5;
+    /** Indicates that this holds a float. */
+    public static final int FLOAT_VALUE_TYPE = 6;
+    /** Indicates that this holds a List of StatsDimensionsValues. */
+    public static final int TUPLE_VALUE_TYPE = 7;
+
+    /** Value of a stats_log.proto DimensionsValue.field. */
+    private final int mField;
+
+    /** Type of stats_log.proto DimensionsValue.value, according to the VALUE_TYPEs above. */
+    private final int mValueType;
+
+    /**
+     * Value of a stats_log.proto DimensionsValue.value.
+     * String, Integer, Long, Boolean, Float, or StatsDimensionsValue[].
+     */
+    private final Object mValue; // immutable or array of immutables
+
+    /**
+     * Creates a {@code StatsDimensionValue} from a parcel.
+     *
+     * @hide
+     */
+    public StatsDimensionsValue(Parcel in) {
+        mField = in.readInt();
+        mValueType = in.readInt();
+        mValue = readValueFromParcel(mValueType, in);
+    }
+
+    /**
+     * Return the field, i.e. the tag of a statsd atom.
+     *
+     * @return the field
+     */
+    public int getField() {
+        return mField;
+    }
+
+    /**
+     * Retrieve the String held, if any.
+     *
+     * @return the {@link String} held if {@link #getValueType()} == {@link #STRING_VALUE_TYPE},
+     *         null otherwise
+     */
+    public String getStringValue() {
+        try {
+            if (mValueType == STRING_VALUE_TYPE) return (String) mValue;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return null;
+    }
+
+    /**
+     * Retrieve the int held, if any.
+     *
+     * @return the int held if {@link #getValueType()} == {@link #INT_VALUE_TYPE}, 0 otherwise
+     */
+    public int getIntValue() {
+        try {
+            if (mValueType == INT_VALUE_TYPE) return (Integer) mValue;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return 0;
+    }
+
+    /**
+     * Retrieve the long held, if any.
+     *
+     * @return the long held if {@link #getValueType()} == {@link #LONG_VALUE_TYPE}, 0 otherwise
+     */
+    public long getLongValue() {
+        try {
+            if (mValueType == LONG_VALUE_TYPE) return (Long) mValue;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return 0;
+    }
+
+    /**
+     * Retrieve the boolean held, if any.
+     *
+     * @return the boolean held if {@link #getValueType()} == {@link #BOOLEAN_VALUE_TYPE},
+     *         false otherwise
+     */
+    public boolean getBooleanValue() {
+        try {
+            if (mValueType == BOOLEAN_VALUE_TYPE) return (Boolean) mValue;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return false;
+    }
+
+    /**
+     * Retrieve the float held, if any.
+     *
+     * @return the float held if {@link #getValueType()} == {@link #FLOAT_VALUE_TYPE}, 0 otherwise
+     */
+    public float getFloatValue() {
+        try {
+            if (mValueType == FLOAT_VALUE_TYPE) return (Float) mValue;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return 0;
+    }
+
+    /**
+     * Retrieve the tuple, in the form of a {@link List} of {@link StatsDimensionsValue}, held,
+     * if any.
+     *
+     * @return the {@link List} of {@link StatsDimensionsValue} held
+     *         if {@link #getValueType()} == {@link #TUPLE_VALUE_TYPE},
+     *         null otherwise
+     */
+    public List<StatsDimensionsValue> getTupleValueList() {
+        if (mValueType != TUPLE_VALUE_TYPE) {
+            return null;
+        }
+        try {
+            StatsDimensionsValue[] orig = (StatsDimensionsValue[]) mValue;
+            List<StatsDimensionsValue> copy = new ArrayList<>(orig.length);
+            // Shallow copy since StatsDimensionsValue is immutable anyway
+            for (int i = 0; i < orig.length; i++) {
+                copy.add(orig[i]);
+            }
+            return copy;
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+            return null;
+        }
+    }
+
+    /**
+     * Returns the constant representing the type of value stored, namely one of
+     * <ul>
+     *   <li>{@link #STRING_VALUE_TYPE}</li>
+     *   <li>{@link #INT_VALUE_TYPE}</li>
+     *   <li>{@link #LONG_VALUE_TYPE}</li>
+     *   <li>{@link #BOOLEAN_VALUE_TYPE}</li>
+     *   <li>{@link #FLOAT_VALUE_TYPE}</li>
+     *   <li>{@link #TUPLE_VALUE_TYPE}</li>
+     * </ul>
+     *
+     * @return the constant representing the type of value stored
+     */
+    public int getValueType() {
+        return mValueType;
+    }
+
+    /**
+     * Returns whether the type of value stored is equal to the given type.
+     *
+     * @param valueType int representing the type of value stored, as used in {@link #getValueType}
+     * @return true if {@link #getValueType()} is equal to {@code valueType}.
+     */
+    public boolean isValueType(int valueType) {
+        return mValueType == valueType;
+    }
+
+    /**
+     * Returns a String representing the information in this StatsDimensionValue.
+     * No guarantees are made about the format of this String.
+     *
+     * @return String representation
+     *
+     * @hide
+     */
+    // Follows the format of statsd's dimension.h toString.
+    public String toString() {
+        try {
+            StringBuilder sb = new StringBuilder();
+            sb.append(mField);
+            sb.append(":");
+            if (mValueType == TUPLE_VALUE_TYPE) {
+                sb.append("{");
+                StatsDimensionsValue[] sbvs = (StatsDimensionsValue[]) mValue;
+                for (int i = 0; i < sbvs.length; i++) {
+                    sb.append(sbvs[i].toString());
+                    sb.append("|");
+                }
+                sb.append("}");
+            } else {
+                sb.append(mValue.toString());
+            }
+            return sb.toString();
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "Failed to successfully get value", e);
+        }
+        return "";
+    }
+
+    /**
+     * Parcelable Creator for StatsDimensionsValue.
+     */
+    public static final Parcelable.Creator<StatsDimensionsValue> CREATOR = new
+            Parcelable.Creator<StatsDimensionsValue>() {
+                public StatsDimensionsValue createFromParcel(Parcel in) {
+                    return new StatsDimensionsValue(in);
+                }
+
+                public StatsDimensionsValue[] newArray(int size) {
+                    return new StatsDimensionsValue[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mField);
+        out.writeInt(mValueType);
+        writeValueToParcel(mValueType, mValue, out, flags);
+    }
+
+    /** Writes mValue to a parcel. Returns true if succeeds. */
+    private static boolean writeValueToParcel(int valueType, Object value, Parcel out, int flags) {
+        try {
+            switch (valueType) {
+                case STRING_VALUE_TYPE:
+                    out.writeString((String) value);
+                    return true;
+                case INT_VALUE_TYPE:
+                    out.writeInt((Integer) value);
+                    return true;
+                case LONG_VALUE_TYPE:
+                    out.writeLong((Long) value);
+                    return true;
+                case BOOLEAN_VALUE_TYPE:
+                    out.writeBoolean((Boolean) value);
+                    return true;
+                case FLOAT_VALUE_TYPE:
+                    out.writeFloat((Float) value);
+                    return true;
+                case TUPLE_VALUE_TYPE: {
+                    StatsDimensionsValue[] values = (StatsDimensionsValue[]) value;
+                    out.writeInt(values.length);
+                    for (int i = 0; i < values.length; i++) {
+                        values[i].writeToParcel(out, flags);
+                    }
+                    return true;
+                }
+                default:
+                    Slog.w(TAG, "readValue of an impossible type " + valueType);
+                    return false;
+            }
+        } catch (ClassCastException e) {
+            Slog.w(TAG, "writeValue cast failed", e);
+            return false;
+        }
+    }
+
+    /** Reads mValue from a parcel. */
+    private static Object readValueFromParcel(int valueType, Parcel parcel) {
+        switch (valueType) {
+            case STRING_VALUE_TYPE:
+                return parcel.readString();
+            case INT_VALUE_TYPE:
+                return parcel.readInt();
+            case LONG_VALUE_TYPE:
+                return parcel.readLong();
+            case BOOLEAN_VALUE_TYPE:
+                return parcel.readBoolean();
+            case FLOAT_VALUE_TYPE:
+                return parcel.readFloat();
+            case TUPLE_VALUE_TYPE: {
+                final int sz = parcel.readInt();
+                StatsDimensionsValue[] values = new StatsDimensionsValue[sz];
+                for (int i = 0; i < sz; i++) {
+                    values[i] = new StatsDimensionsValue(parcel);
+                }
+                return values;
+            }
+            default:
+                Slog.w(TAG, "readValue of an impossible type " + valueType);
+                return null;
+        }
+    }
+}
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index d0c2870..d592000 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,7 +1,10 @@
 package android.os;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.os.WorkSourceProto;
+import android.provider.Settings;
+import android.provider.Settings.Global;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
@@ -109,6 +112,17 @@
         }
     }
 
+    /**
+     * Whether system services should create {@code WorkChains} (wherever possible) in the place
+     * of flat UID lists.
+     *
+     * @hide
+     */
+    public static boolean isChainedBatteryAttributionEnabled(Context context) {
+        return Settings.Global.getInt(context.getContentResolver(),
+                Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED, 0) == 1;
+    }
+
     /** @hide */
     public int size() {
         return mNum;
@@ -479,6 +493,29 @@
         return mChains;
     }
 
+    /**
+     * DO NOT USE: Hacky API provided solely for {@code GnssLocationProvider}. See
+     * {@code setReturningDiffs} as well.
+     *
+     * @hide
+     */
+    public void transferWorkChains(WorkSource other) {
+        if (mChains != null) {
+            mChains.clear();
+        }
+
+        if (other.mChains == null || other.mChains.isEmpty()) {
+            return;
+        }
+
+        if (mChains == null) {
+            mChains = new ArrayList<>(4);
+        }
+
+        mChains.addAll(other.mChains);
+        other.mChains.clear();
+    }
+
     private boolean removeUids(WorkSource other) {
         int N1 = mNum;
         final int[] uids1 = mUids;
@@ -866,6 +903,13 @@
             return mUids[0];
         }
 
+        /**
+         * Return the tag associated with the attribution UID. See (@link #getAttributionUid}.
+         */
+        public String getAttributionTag() {
+            return mTags[0];
+        }
+
         // TODO: The following three trivial getters are purely for testing and will be removed
         // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
         // diffing it etc.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index daf6bd5..cadca24 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9155,6 +9155,22 @@
                BOOLEAN_VALIDATOR;
 
        /**
+        * Whether to notify the user of carrier networks.
+        * <p>
+        * If not connected and the scan results have a carrier network, we will
+        * put this notification up. If we attempt to connect to a network or
+        * the carrier network(s) disappear, we remove the notification. When we
+        * show the notification, we will not show it again for
+        * {@link android.provider.Settings.Global#WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY} time.
+        * @hide
+        */
+       public static final String WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON =
+               "wifi_carrier_networks_available_notification_on";
+
+       private static final Validator WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR =
+               BOOLEAN_VALIDATOR;
+
+       /**
         * {@hide}
         */
        public static final String WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON =
@@ -9335,13 +9351,52 @@
         public static final String RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS =
                 "recommended_network_evaluator_cache_expiry_ms";
 
-       /**
+        /**
         * Settings to allow BLE scans to be enabled even when Bluetooth is turned off for
         * connectivity.
         * @hide
         */
-       public static final String BLE_SCAN_ALWAYS_AVAILABLE =
-               "ble_scan_always_enabled";
+        public static final String BLE_SCAN_ALWAYS_AVAILABLE = "ble_scan_always_enabled";
+
+        /**
+         * The length in milliseconds of a BLE scan window in a low-power scan mode.
+         * @hide
+         */
+        public static final String BLE_SCAN_LOW_POWER_WINDOW_MS = "ble_scan_low_power_window_ms";
+
+        /**
+         * The length in milliseconds of a BLE scan window in a balanced scan mode.
+         * @hide
+         */
+        public static final String BLE_SCAN_BALANCED_WINDOW_MS = "ble_scan_balanced_window_ms";
+
+        /**
+         * The length in milliseconds of a BLE scan window in a low-latency scan mode.
+         * @hide
+         */
+        public static final String BLE_SCAN_LOW_LATENCY_WINDOW_MS =
+                "ble_scan_low_latency_window_ms";
+
+        /**
+         * The length in milliseconds of a BLE scan interval in a low-power scan mode.
+         * @hide
+         */
+        public static final String BLE_SCAN_LOW_POWER_INTERVAL_MS =
+                "ble_scan_low_power_interval_ms";
+
+        /**
+         * The length in milliseconds of a BLE scan interval in a balanced scan mode.
+         * @hide
+         */
+        public static final String BLE_SCAN_BALANCED_INTERVAL_MS =
+                "ble_scan_balanced_interval_ms";
+
+        /**
+         * The length in milliseconds of a BLE scan interval in a low-latency scan mode.
+         * @hide
+         */
+        public static final String BLE_SCAN_LOW_LATENCY_INTERVAL_MS =
+                "ble_scan_low_latency_interval_ms";
 
        /**
         * Used to save the Wifi_ON state prior to tethering.
@@ -11193,6 +11248,16 @@
          */
         public static final String OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION =
                 "override_settings_provider_restore_any_version";
+        /**
+         * Flag to toggle whether system services report attribution chains when they attribute
+         * battery use via a {@code WorkSource}.
+         *
+         * Type: int (0 to disable, 1 to enable)
+         *
+         * @hide
+         */
+        public static final String CHAINED_BATTERY_ATTRIBUTION_ENABLED =
+                "chained_battery_attribution_enabled";
 
         /**
          * Settings to backup. This is here so that it's in the same place as the settings
@@ -11222,6 +11287,7 @@
             NETWORK_RECOMMENDATIONS_ENABLED,
             WIFI_WAKEUP_ENABLED,
             WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+            WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON,
             USE_OPEN_WIFI_PACKAGE,
             WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
             EMERGENCY_TONE,
@@ -11268,6 +11334,8 @@
             VALIDATORS.put(PRIVATE_DNS_MODE, PRIVATE_DNS_MODE_VALIDATOR);
             VALIDATORS.put(PRIVATE_DNS_SPECIFIER, PRIVATE_DNS_SPECIFIER_VALIDATOR);
             VALIDATORS.put(SOFT_AP_TIMEOUT_ENABLED, SOFT_AP_TIMEOUT_ENABLED_VALIDATOR);
+            VALIDATORS.put(WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON,
+                    WIFI_CARRIER_NETWORKS_AVAILABLE_NOTIFICATION_ON_VALIDATOR);
         }
 
         /**
diff --git a/core/java/android/util/StatsManager.java b/core/java/android/util/StatsManager.java
index e0d085c..687aa83 100644
--- a/core/java/android/util/StatsManager.java
+++ b/core/java/android/util/StatsManager.java
@@ -17,19 +17,33 @@
 
 import android.Manifest;
 import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
 import android.os.IBinder;
 import android.os.IStatsManager;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 
+
+/*
+ *
+ *
+ *
+ *
+ * THIS ENTIRE FILE IS ONLY TEMPORARY TO PREVENT BREAKAGES OF DEPENDENCIES ON OLD APIS.
+ * The new StatsManager is to be found in android.app.StatsManager.
+ * TODO: Delete this file!
+ *
+ *
+ *
+ *
+ */
+
+
 /**
  * API for StatsD clients to send configurations and retrieve data.
  *
  * @hide
  */
-@SystemApi
-public final class StatsManager {
+public class StatsManager {
     IStatsManager mService;
     private static final String TAG = "StatsManager";
 
@@ -55,7 +69,7 @@
      * Clients can send a configuration and simultaneously registers the name of a broadcast
      * receiver that listens for when it should request data.
      *
-     * @param configKey An arbitrary string that allows clients to track the configuration.
+     * @param configKey An arbitrary integer that allows clients to track the configuration.
      * @param config    Wire-encoded StatsDConfig proto that specifies metrics (and all
      *                  dependencies eg, conditions and matchers).
      * @param pkg       The package name to receive the broadcast.
diff --git a/core/java/android/util/apk/ApkVerityBuilder.java b/core/java/android/util/apk/ApkVerityBuilder.java
index 5880c6a..ba21ccb 100644
--- a/core/java/android/util/apk/ApkVerityBuilder.java
+++ b/core/java/android/util/apk/ApkVerityBuilder.java
@@ -70,7 +70,7 @@
             throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
         long signingBlockSize =
                 signatureInfo.centralDirOffset - signatureInfo.apkSigningBlockOffset;
-        long dataSize = apk.length() - signingBlockSize - ZIP_EOCD_CENTRAL_DIR_OFFSET_FIELD_SIZE;
+        long dataSize = apk.length() - signingBlockSize;
         int[] levelOffset = calculateVerityLevelOffset(dataSize);
 
         ByteBuffer output = bufferFactory.create(
@@ -132,11 +132,13 @@
         }
 
         if (headerOutput != null) {
+            headerOutput.order(ByteOrder.LITTLE_ENDIAN);
             generateFsverityHeader(headerOutput, apk.length(), levelOffset.length - 1,
                     DEFAULT_SALT);
         }
 
         if (extensionsOutput != null) {
+            extensionsOutput.order(ByteOrder.LITTLE_ENDIAN);
             generateFsverityExtensions(extensionsOutput, signatureInfo.apkSigningBlockOffset,
                     signingBlockSize, signatureInfo.eocdOffset);
         }
@@ -306,6 +308,14 @@
         return rootHash;
     }
 
+    private static void bufferPut(ByteBuffer buffer, byte value) {
+        // FIXME(b/72459251): buffer.put(value) does NOT work surprisingly. The position() after put
+        // does NOT even change. This hack workaround the problem, but the root cause remains
+        // unkonwn yet.  This seems only happen when it goes through the apk install flow on my
+        // setup.
+        buffer.put(new byte[] { value });
+    }
+
     private static ByteBuffer generateFsverityHeader(ByteBuffer buffer, long fileSize, int depth,
             byte[] salt) {
         if (salt.length != 8) {
@@ -315,22 +325,23 @@
         // TODO(b/30972906): update the reference when there is a better one in public.
         buffer.put("TrueBrew".getBytes());  // magic
 
-        buffer.put((byte) 1);        // major version
-        buffer.put((byte) 0);        // minor version
-        buffer.put((byte) 12);       // log2(block-size): log2(4096)
-        buffer.put((byte) 7);        // log2(leaves-per-node): log2(4096 / 32)
+        bufferPut(buffer, (byte) 1);        // major version
+        bufferPut(buffer, (byte) 0);        // minor version
+        bufferPut(buffer, (byte) 12);       // log2(block-size): log2(4096)
+        bufferPut(buffer, (byte) 7);        // log2(leaves-per-node): log2(4096 / 32)
 
-        buffer.putShort((short) 1);  // meta algorithm, SHA256_MODE == 1
-        buffer.putShort((short) 1);  // data algorithm, SHA256_MODE == 1
+        buffer.putShort((short) 1);         // meta algorithm, SHA256_MODE == 1
+        buffer.putShort((short) 1);         // data algorithm, SHA256_MODE == 1
 
-        buffer.putInt(0x1);          // flags, 0x1: has extension
-        buffer.putInt(0);            // reserved
+        buffer.putInt(0x1);                 // flags, 0x1: has extension
+        buffer.putInt(0);                   // reserved
 
-        buffer.putLong(fileSize);    // original file size
+        buffer.putLong(fileSize);           // original file size
 
-        buffer.put((byte) 0);        // auth block offset, disabled here
-        buffer.put(salt);            // salt (8 bytes)
-        // skip(buffer, 22);            // reserved
+        bufferPut(buffer, (byte) 0);        // auth block offset, disabled here
+        bufferPut(buffer, (byte) 2);        // extension count
+        buffer.put(salt);                   // salt (8 bytes)
+        // skip(buffer, 22);                // reserved
 
         buffer.rewind();
         return buffer;
@@ -340,11 +351,6 @@
             long signingBlockSize, long eocdOffset) {
         // Snapshot of the FSVerity structs (subject to change once upstreamed).
         //
-        // struct fsverity_header_extension {
-        //   u8 extension_count;
-        //   u8 reserved[7];
-        // };
-        //
         // struct fsverity_extension {
         //   __le16 length;
         //   u8 type;
@@ -363,10 +369,6 @@
         //   u8 databytes[];
         // };
 
-        // struct fsverity_header_extension
-        buffer.put((byte) 2);        // extension count
-        skip(buffer, 3);             // reserved
-
         final int kSizeOfFsverityExtensionHeader = 8;
 
         {
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 17b6ddc..d7fd329 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -157,10 +157,8 @@
      * @param flags See {@code View#startDragAndDrop}
      * @param surface Surface containing drag shadow image
      * @param touchSource See {@code InputDevice#getSource()}
-     * @param touchX TODO (b/72072998): Fix the issue that the system server misuse the arguments as
-     *         initial touch point while the framework passes drag shadow size.
-     * @param touchY TODO (b/72072998): Fix the issue that the system server misuse the arguments as
-     *         initial touch point while the framework passes drag shadow size.
+     * @param touchX X coordinate of last touch point
+     * @param touchY Y coordinate of last touch point
      * @param thumbCenterX X coordinate for the position within the shadow image that should be
      *         underneath the touch point during the drag and drop operation.
      * @param thumbCenterY Y coordinate for the position within the shadow image that should be
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 578679b..4a9da4a 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -25,6 +25,7 @@
 import android.content.res.CompatibilityInfo.Translator;
 import android.content.res.Configuration;
 import android.graphics.Canvas;
+import android.graphics.Color;
 import android.graphics.PixelFormat;
 import android.graphics.PorterDuff;
 import android.graphics.Rect;
@@ -114,7 +115,7 @@
     final Rect mScreenRect = new Rect();
     SurfaceSession mSurfaceSession;
 
-    SurfaceControl mSurfaceControl;
+    SurfaceControlWithBackground mSurfaceControl;
     // In the case of format changes we switch out the surface in-place
     // we need to preserve the old one until the new one has drawn.
     SurfaceControl mDeferredDestroySurfaceControl;
@@ -925,6 +926,17 @@
         return mSubLayer >= 0;
     }
 
+    /**
+     * Set an opaque background color to use with this {@link SurfaceView} when it's being resized
+     * and size of the content hasn't updated yet. This color will fill the expanded area when the
+     * view becomes larger.
+     * @param bgColor An opaque color to fill the background. Alpha component will be ignored.
+     * @hide
+     */
+    public void setResizeBackgroundColor(int bgColor) {
+        mSurfaceControl.setBackgroundColor(bgColor);
+    }
+
     private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
         private static final String LOG_TAG = "SurfaceHolder";
 
@@ -1219,6 +1231,19 @@
             mBackgroundControl.deferTransactionUntil(barrier, frame);
         }
 
+        /** Set the color to fill the background with. */
+        private void setBackgroundColor(int bgColor) {
+            final float[] colorComponents = new float[] { Color.red(bgColor) / 255.f,
+                    Color.green(bgColor) / 255.f, Color.blue(bgColor) / 255.f };
+
+            SurfaceControl.openTransaction();
+            try {
+                mBackgroundControl.setColor(colorComponents);
+            } finally {
+                SurfaceControl.closeTransaction();
+            }
+        }
+
         void updateBackgroundVisibility() {
             if (mOpaque && mVisible) {
                 mBackgroundControl.show();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a2ecfc4..3d6a6fe 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16,6 +16,8 @@
 
 package android.view;
 
+import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
+
 import static java.lang.Math.max;
 
 import android.animation.AnimatorInflater;
@@ -8548,6 +8550,12 @@
         info.setLongClickable(isLongClickable());
         info.setContextClickable(isContextClickable());
         info.setLiveRegion(getAccessibilityLiveRegion());
+        if ((mTooltipInfo != null) && (mTooltipInfo.mTooltipText != null)) {
+            info.setTooltipText(mTooltipInfo.mTooltipText);
+            info.addAction((mTooltipInfo.mTooltipPopup == null)
+                    ? AccessibilityNodeInfo.AccessibilityAction.ACTION_SHOW_TOOLTIP
+                    : AccessibilityNodeInfo.AccessibilityAction.ACTION_HIDE_TOOLTIP);
+        }
 
         // TODO: These make sense only if we are in an AdapterView but all
         // views can be selected. Maybe from accessibility perspective
@@ -8951,8 +8959,7 @@
             return;
         }
         mAccessibilityTraversalBeforeId = beforeId;
-        notifyAccessibilityStateChanged(
-                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+        notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
     /**
@@ -8995,8 +9002,7 @@
             return;
         }
         mAccessibilityTraversalAfterId = afterId;
-        notifyAccessibilityStateChanged(
-                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+        notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
     /**
@@ -9038,8 +9044,7 @@
                 && mID == View.NO_ID) {
             mID = generateViewId();
         }
-        notifyAccessibilityStateChanged(
-                AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+        notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
     /**
@@ -10539,7 +10544,7 @@
 
         if (pflags3 != mPrivateFlags3) {
             mPrivateFlags3 = pflags3;
-            notifyAccessibilityStateChanged(AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+            notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
         }
     }
 
@@ -11253,6 +11258,8 @@
 
         if (!isLayoutValid()) {
             mPrivateFlags |= PFLAG_WANTS_FOCUS;
+        } else {
+            clearParentsWantFocus();
         }
 
         handleFocusGainInternal(direction, previouslyFocusedRect);
@@ -11367,8 +11374,7 @@
             mPrivateFlags2 &= ~PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
             mPrivateFlags2 |= (mode << PFLAG2_ACCESSIBILITY_LIVE_REGION_SHIFT)
                     & PFLAG2_ACCESSIBILITY_LIVE_REGION_MASK;
-            notifyAccessibilityStateChanged(
-                    AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+            notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
         }
     }
 
@@ -11427,8 +11433,7 @@
             if (!maySkipNotify || oldIncludeForAccessibility != includeForAccessibility()) {
                 notifyAccessibilitySubtreeChanged();
             } else {
-                notifyAccessibilityStateChanged(
-                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+                notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
             }
         }
     }
@@ -11838,8 +11843,7 @@
                         || getAccessibilitySelectionEnd() != end)
                         && (start == end)) {
                     setAccessibilitySelection(start, end);
-                    notifyAccessibilityStateChanged(
-                            AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+                    notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
                     return true;
                 }
             } break;
@@ -11856,6 +11860,21 @@
                     return true;
                 }
             } break;
+            case R.id.accessibilityActionShowTooltip: {
+                if ((mTooltipInfo != null) && (mTooltipInfo.mTooltipPopup != null)) {
+                    // Tooltip already showing
+                    return false;
+                }
+                return showLongClickTooltip(0, 0);
+            }
+            case R.id.accessibilityActionHideTooltip: {
+                if ((mTooltipInfo == null) || (mTooltipInfo.mTooltipPopup == null)) {
+                    // No tooltip showing
+                    return false;
+                }
+                hideTooltip();
+                return true;
+            }
         }
         return false;
     }
@@ -13889,12 +13908,10 @@
                 if (oldIncludeForAccessibility != includeForAccessibility()) {
                     notifyAccessibilitySubtreeChanged();
                 } else {
-                    notifyAccessibilityStateChanged(
-                            AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+                    notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
                 }
             } else if ((changed & ENABLED_MASK) != 0) {
-                notifyAccessibilityStateChanged(
-                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+                notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
             }
         }
     }
@@ -21854,8 +21871,7 @@
             if (selected) {
                 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
             } else {
-                notifyAccessibilityStateChanged(
-                        AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
+                notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
             }
         }
     }
@@ -27057,6 +27073,8 @@
         final boolean fromTouch = (mPrivateFlags3 & PFLAG3_FINGER_DOWN) == PFLAG3_FINGER_DOWN;
         mTooltipInfo.mTooltipPopup.show(this, x, y, fromTouch, mTooltipInfo.mTooltipText);
         mAttachInfo.mTooltipHost = this;
+        // The available accessibility actions have changed
+        notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
         return true;
     }
 
@@ -27075,6 +27093,8 @@
         if (mAttachInfo != null) {
             mAttachInfo.mTooltipHost = null;
         }
+        // The available accessibility actions have changed
+        notifyAccessibilityStateChanged(CONTENT_CHANGE_TYPE_UNDEFINED);
     }
 
     private boolean showLongClickTooltip(int x, int y) {
@@ -27083,8 +27103,8 @@
         return showTooltip(x, y, true);
     }
 
-    private void showHoverTooltip() {
-        showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false);
+    private boolean showHoverTooltip() {
+        return showTooltip(mTooltipInfo.mAnchorX, mTooltipInfo.mAnchorY, false);
     }
 
     boolean dispatchTooltipHoverEvent(MotionEvent event) {
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 3ee282e..23e7d61 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -727,6 +727,7 @@
     private CharSequence mError;
     private CharSequence mPaneTitle;
     private CharSequence mContentDescription;
+    private CharSequence mTooltipText;
     private String mViewIdResourceName;
     private ArrayList<String> mExtraDataKeys;
 
@@ -2655,6 +2656,34 @@
     }
 
     /**
+     * Gets the tooltip text of this node.
+     *
+     * @return The tooltip text.
+     */
+    @Nullable
+    public CharSequence getTooltipText() {
+        return mTooltipText;
+    }
+
+    /**
+     * Sets the tooltip text of this node.
+     * <p>
+     *   <strong>Note:</strong> Cannot be called from an
+     *   {@link android.accessibilityservice.AccessibilityService}.
+     *   This class is made immutable before being delivered to an AccessibilityService.
+     * </p>
+     *
+     * @param tooltipText The tooltip text.
+     *
+     * @throws IllegalStateException If called from an AccessibilityService.
+     */
+    public void setTooltipText(@Nullable CharSequence tooltipText) {
+        enforceNotSealed();
+        mTooltipText = (tooltipText == null) ? null
+                : tooltipText.subSequence(0, tooltipText.length());
+    }
+
+    /**
      * Sets the view for which the view represented by this info serves as a
      * label for accessibility purposes.
      *
@@ -3209,6 +3238,10 @@
             nonDefaultFields |= bitAt(fieldIndex);
         }
         fieldIndex++;
+        if (!Objects.equals(mTooltipText, DEFAULT.mTooltipText)) {
+            nonDefaultFields |= bitAt(fieldIndex);
+        }
+        fieldIndex++;
         if (!Objects.equals(mViewIdResourceName, DEFAULT.mViewIdResourceName)) {
             nonDefaultFields |= bitAt(fieldIndex);
         }
@@ -3329,6 +3362,8 @@
             parcel.writeCharSequence(mContentDescription);
         }
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mPaneTitle);
+        if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeCharSequence(mTooltipText);
+
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeString(mViewIdResourceName);
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) parcel.writeInt(mTextSelectionStart);
@@ -3401,6 +3436,7 @@
         mError = other.mError;
         mContentDescription = other.mContentDescription;
         mPaneTitle = other.mPaneTitle;
+        mTooltipText = other.mTooltipText;
         mViewIdResourceName = other.mViewIdResourceName;
 
         if (mActions != null) mActions.clear();
@@ -3522,6 +3558,7 @@
             mContentDescription = parcel.readCharSequence();
         }
         if (isBitSet(nonDefaultFields, fieldIndex++)) mPaneTitle = parcel.readString();
+        if (isBitSet(nonDefaultFields, fieldIndex++)) mTooltipText = parcel.readCharSequence();
         if (isBitSet(nonDefaultFields, fieldIndex++)) mViewIdResourceName = parcel.readString();
 
         if (isBitSet(nonDefaultFields, fieldIndex++)) mTextSelectionStart = parcel.readInt();
@@ -3684,6 +3721,10 @@
                 return "ACTION_SET_PROGRESS";
             case R.id.accessibilityActionContextClick:
                 return "ACTION_CONTEXT_CLICK";
+            case R.id.accessibilityActionShowTooltip:
+                return "ACTION_SHOW_TOOLTIP";
+            case R.id.accessibilityActionHideTooltip:
+                return "ACTION_HIDE_TOOLTIP";
             default:
                 return "ACTION_UNKNOWN";
         }
@@ -3797,6 +3838,7 @@
         builder.append("; error: ").append(mError);
         builder.append("; maxTextLength: ").append(mMaxTextLength);
         builder.append("; contentDescription: ").append(mContentDescription);
+        builder.append("; tooltipText: ").append(mTooltipText);
         builder.append("; viewIdResName: ").append(mViewIdResourceName);
 
         builder.append("; checkable: ").append(isCheckable());
@@ -4211,6 +4253,20 @@
         public static final AccessibilityAction ACTION_MOVE_WINDOW =
                 new AccessibilityAction(R.id.accessibilityActionMoveWindow);
 
+        /**
+         * Action to show a tooltip. A node should expose this action only for views with tooltip
+         * text that but are not currently showing a tooltip.
+         */
+        public static final AccessibilityAction ACTION_SHOW_TOOLTIP =
+                new AccessibilityAction(R.id.accessibilityActionShowTooltip);
+
+        /**
+         * Action to hide a tooltip. A node should expose this action only for views that are
+         * currently showing a tooltip.
+         */
+        public static final AccessibilityAction ACTION_HIDE_TOOLTIP =
+                new AccessibilityAction(R.id.accessibilityActionHideTooltip);
+
         private final int mActionId;
         private final CharSequence mLabel;
 
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index e554540..eba9176 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -21,6 +21,7 @@
 import android.inputmethodservice.InputMethodService;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.LocaleList;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 
@@ -898,4 +899,37 @@
      */
     boolean commitContent(@NonNull InputContentInfo inputContentInfo, int flags,
             @Nullable Bundle opts);
+
+    /**
+     * Called by the input method to tell a hint about the locales of text to be committed.
+     *
+     * <p>This is just a hint for editor authors (and the system) to choose better options when
+     * they have to disambiguate languages, like editor authors can do for input methods with
+     * {@link EditorInfo#hintLocales}.</p>
+     *
+     * <p>The language hint provided by this callback should have higher priority than
+     * {@link InputMethodSubtype#getLanguageTag()}, which cannot be updated dynamically.</p>
+     *
+     * <p>Note that in general it is discouraged for input method to specify
+     * {@link android.text.style.LocaleSpan} when inputting text, mainly because of application
+     * compatibility concerns.</p>
+     * <ul>
+     *     <li>When an existing text that already has {@link android.text.style.LocaleSpan} is being
+     *     modified by both the input method and application, there is no reliable and easy way to
+     *     keep track of who modified {@link android.text.style.LocaleSpan}. For instance, if the
+     *     text was updated by JavaScript, it it highly likely that span information is completely
+     *     removed, while some input method attempts to preserve spans if possible.</li>
+     *     <li>There is no clear semantics regarding whether {@link android.text.style.LocaleSpan}
+     *     means a weak (ignorable) hint or a strong hint. This becomes more problematic when
+     *     multiple {@link android.text.style.LocaleSpan} instances are specified to the same
+     *     text region, especially when those spans are conflicting.</li>
+     * </ul>
+     * @param languageHint list of languages sorted by the priority and/or probability
+     */
+    default void reportLanguageHint(@NonNull LocaleList languageHint) {
+        // Intentionally empty.
+        //
+        // We need to have *some* default implementation for the source compatibility.
+        // See Bug 72127682 for details.
+    }
 }
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index f671e22..cbe6856 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -16,8 +16,10 @@
 
 package android.view.inputmethod;
 
+import android.annotation.NonNull;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.LocaleList;
 import android.view.KeyEvent;
 
 /**
@@ -303,4 +305,13 @@
     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
         return mTarget.commitContent(inputContentInfo, flags, opts);
     }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
+    public void reportLanguageHint(@NonNull LocaleList languageHint) {
+        mTarget.reportLanguageHint(languageHint);
+    }
 }
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index 90cc481..cba11a8 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -931,12 +931,12 @@
      * Note that this setting affects only JavaScript access to file scheme
      * resources. Other access to such resources, for example, from image HTML
      * elements, is unaffected. To prevent possible violation of same domain policy
-     * on {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} and earlier
-     * devices, you should explicitly set this value to {@code false}.
+     * when targeting {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and earlier,
+     * you should explicitly set this value to {@code false}.
      * <p>
-     * The default value is {@code true} for API level
+     * The default value is {@code true} for apps targeting
      * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below,
-     * and {@code false} for API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
+     * and {@code false} when targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
      * and above.
      *
      * @param flag whether JavaScript running in the context of a file scheme
@@ -947,18 +947,18 @@
     /**
      * Sets whether JavaScript running in the context of a file scheme URL
      * should be allowed to access content from other file scheme URLs. To
-     * enable the most restrictive, and therefore secure policy, this setting
+     * enable the most restrictive, and therefore secure, policy this setting
      * should be disabled. Note that the value of this setting is ignored if
      * the value of {@link #getAllowUniversalAccessFromFileURLs} is {@code true}.
      * Note too, that this setting affects only JavaScript access to file scheme
      * resources. Other access to such resources, for example, from image HTML
      * elements, is unaffected. To prevent possible violation of same domain policy
-     * on {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH} and earlier
-     * devices, you should explicitly set this value to {@code false}.
+     * when targeting {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and earlier,
+     * you should explicitly set this value to {@code false}.
      * <p>
-     * The default value is {@code true} for API level
+     * The default value is {@code true} for apps targeting
      * {@link android.os.Build.VERSION_CODES#ICE_CREAM_SANDWICH_MR1} and below,
-     * and {@code false} for API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
+     * and {@code false} when targeting {@link android.os.Build.VERSION_CODES#JELLY_BEAN}
      * and above.
      *
      * @param flag whether JavaScript running in the context of a file scheme
@@ -1394,26 +1394,26 @@
 
 
     /**
-     * Sets whether Safe Browsing is enabled. Safe browsing allows WebView to
+     * Sets whether Safe Browsing is enabled. Safe Browsing allows WebView to
      * protect against malware and phishing attacks by verifying the links.
      *
      * <p>
-     * Safe browsing is disabled by default. The recommended way to enable Safe browsing is using a
-     * manifest tag to change the default value to enabled for all WebViews (read <a
-     * href="{@docRoot}reference/android/webkit/WebView.html">general Safe Browsing info</a>).
+     * Safe Browsing can be disabled for all WebViews using a manifest tag (read <a
+     * href="{@docRoot}reference/android/webkit/WebView.html">general Safe Browsing info</a>). The
+     * manifest tag has a lower precedence than this API.
      *
      * <p>
-     * This API overrides the manifest tag value for this WebView.
+     * Safe Browsing is enabled by default for devices which support it.
      *
-     * @param enabled Whether Safe browsing is enabled.
+     * @param enabled Whether Safe Browsing is enabled.
      */
     public abstract void setSafeBrowsingEnabled(boolean enabled);
 
     /**
-     * Gets whether Safe browsing is enabled.
+     * Gets whether Safe Browsing is enabled.
      * See {@link #setSafeBrowsingEnabled}.
      *
-     * @return {@code true} if Safe browsing is enabled and {@code false} otherwise.
+     * @return {@code true} if Safe Browsing is enabled and {@code false} otherwise.
      */
     public abstract boolean getSafeBrowsingEnabled();
 
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 2c51ee9..d2cb70e 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -327,22 +327,25 @@
  * <h3>Safe Browsing</h3>
  *
  * <p>
- * If Safe Browsing is enabled, WebView will block malicious URLs and present a warning UI to the
- * user to allow them to navigate back safely or proceed to the malicious page.
+ * With Safe Browsing, WebView will block malicious URLs and present a warning UI to the user to
+ * allow them to navigate back safely or proceed to the malicious page.
  * <p>
- * The recommended way for apps to enable the feature is putting the following tag in the manifest's
- * {@code <application>} element:
+ * Safe Browsing is enabled by default on devices which support it. If your app needs to disable
+ * Safe Browsing for all WebViews, it can do so in the manifest's {@code <application>} element:
  * <p>
  * <pre>
  * &lt;manifest&gt;
  *     &lt;application&gt;
  *         ...
  *         &lt;meta-data android:name=&quot;android.webkit.WebView.EnableSafeBrowsing&quot;
- *             android:value=&quot;true&quot; /&gt;
+ *             android:value=&quot;false&quot; /&gt;
  *     &lt;/application&gt;
  * &lt;/manifest&gt;
  * </pre>
  *
+ * <p>
+ * Otherwise, see {@link WebSettings#setSafeBrowsingEnabled}.
+ *
  */
 // Implementation notes.
 // The WebView is a thin API class that delegates its public API to a backend WebViewProvider
@@ -1670,9 +1673,8 @@
      * invoked with {@code true}. Safe Browsing is not fully supported on all devices. For those
      * devices {@code callback} will receive {@code false}.
      * <p>
-     * This does not enable the Safe Browsing feature itself, and should only be called if Safe
-     * Browsing is enabled by the manifest tag or {@link WebSettings#setSafeBrowsingEnabled}. This
-     * prepares resources used for Safe Browsing.
+     * This should not be called if Safe Browsing has been disabled by manifest tag or {@link
+     * WebSettings#setSafeBrowsingEnabled}. This prepares resources used for Safe Browsing.
      * <p>
      * This should be called with the Application Context (and will always use the Application
      * context to do its work regardless).
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 6bee58f..594d240 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -19,6 +19,7 @@
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
 import android.annotation.NonNull;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Configuration;
@@ -30,6 +31,7 @@
 import android.os.Bundle;
 import android.os.Debug;
 import android.os.Handler;
+import android.os.LocaleList;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.StrictMode;
@@ -2744,7 +2746,7 @@
     }
 
     private void drawSelector(Canvas canvas) {
-        if (!mSelectorRect.isEmpty()) {
+        if (shouldDrawSelector()) {
             final Drawable selector = mSelector;
             selector.setBounds(mSelectorRect);
             selector.draw(canvas);
@@ -2752,6 +2754,14 @@
     }
 
     /**
+     * @hide
+     */
+    @TestApi
+    public final boolean shouldDrawSelector() {
+        return !mSelectorRect.isEmpty();
+    }
+
+    /**
      * Controls whether the selection highlight drawable should be drawn on top of the item or
      * behind it.
      *
@@ -6026,6 +6036,11 @@
         public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
             return getTarget().commitContent(inputContentInfo, flags, opts);
         }
+
+        @Override
+        public void reportLanguageHint(@NonNull LocaleList languageHint) {
+            getTarget().reportLanguageHint(languageHint);
+        }
     }
 
     /**
diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl
index d3e807d..514ff76 100644
--- a/core/java/com/android/internal/app/IBatteryStats.aidl
+++ b/core/java/com/android/internal/app/IBatteryStats.aidl
@@ -91,8 +91,7 @@
 
     void noteVibratorOn(int uid, long durationMillis);
     void noteVibratorOff(int uid);
-    void noteStartGps(int uid);
-    void noteStopGps(int uid);
+    void noteGpsChanged(in WorkSource oldSource, in WorkSource newSource);
     void noteGpsSignalQuality(int signalLevel);
     void noteScreenState(int state);
     void noteScreenBrightness(int brightness);
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 147438c..f8117a7 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -306,4 +306,13 @@
      *    operation will immediately be finished with no further attempts to restore app data.
      */
     int abortFullRestore();
+
+    /**
+     * Returns flags with additional information about the transport, which is accessible to the
+     * {@link android.app.backup.BackupAgent}. This allows the agent to decide what to backup or
+     * restore based on properties of the transport.
+     *
+     * <p>For supported flags see {@link android.app.backup.BackupAgent}.
+     */
+    int getTransportFlags();
 }
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 51f51c2..ee3bec8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -4587,8 +4587,35 @@
 
     int mGpsNesting;
 
-    public void noteStartGpsLocked(int uid) {
-        uid = mapUid(uid);
+    public void noteGpsChangedLocked(WorkSource oldWs, WorkSource newWs) {
+        for (int i = 0; i < newWs.size(); ++i) {
+            noteStartGpsLocked(newWs.get(i), null);
+        }
+
+        for (int i = 0; i < oldWs.size(); ++i) {
+            noteStopGpsLocked((oldWs.get(i)), null);
+        }
+
+        List<WorkChain>[] wcs = WorkSource.diffChains(oldWs, newWs);
+        if (wcs != null) {
+            if (wcs[0] != null) {
+                final List<WorkChain> newChains = wcs[0];
+                for (int i = 0; i < newChains.size(); ++i) {
+                    noteStartGpsLocked(-1, newChains.get(i));
+                }
+            }
+
+            if (wcs[1] != null) {
+                final List<WorkChain> goneChains = wcs[1];
+                for (int i = 0; i < goneChains.size(); ++i) {
+                    noteStopGpsLocked(-1, goneChains.get(i));
+                }
+            }
+        }
+    }
+
+    private void noteStartGpsLocked(int uid, WorkChain workChain) {
+        uid = getAttributionUid(uid, workChain);
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         if (mGpsNesting == 0) {
@@ -4598,11 +4625,19 @@
             addHistoryRecordLocked(elapsedRealtime, uptime);
         }
         mGpsNesting++;
+
+        if (workChain == null) {
+            StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, uid, null, 1);
+        } else {
+            StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED,
+                    workChain.getUids(), workChain.getTags(), 1);
+        }
+
         getUidStatsLocked(uid).noteStartGps(elapsedRealtime);
     }
 
-    public void noteStopGpsLocked(int uid) {
-        uid = mapUid(uid);
+    private void noteStopGpsLocked(int uid, WorkChain workChain) {
+        uid = getAttributionUid(uid, workChain);
         final long elapsedRealtime = mClocks.elapsedRealtime();
         final long uptime = mClocks.uptimeMillis();
         mGpsNesting--;
@@ -4614,6 +4649,14 @@
             stopAllGpsSignalQualityTimersLocked(-1);
             mGpsSignalQualityBin = -1;
         }
+
+        if (workChain == null) {
+            StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, uid, null, 0);
+        } else {
+            StatsLog.write(StatsLog.GPS_SCAN_STATE_CHANGED, workChain.getUids(),
+                    workChain.getTags(), 0);
+        }
+
         getUidStatsLocked(uid).noteStopGps(elapsedRealtime);
     }
 
@@ -9842,9 +9885,7 @@
         public void noteStartSensor(int sensor, long elapsedRealtimeMs) {
             DualTimer t = getSensorTimerLocked(sensor, /* create= */ true);
             t.startRunningLocked(elapsedRealtimeMs);
-            if (sensor == Sensor.GPS) {
-                StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), null, 1);
-            } else {
+            if (sensor != Sensor.GPS) {
                 StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null, sensor,
                         1);
             }
@@ -9855,14 +9896,9 @@
             DualTimer t = getSensorTimerLocked(sensor, false);
             if (t != null) {
                 t.stopRunningLocked(elapsedRealtimeMs);
-                if (!t.isRunningLocked()) { // only tell statsd if truly stopped
-                    if (sensor == Sensor.GPS) {
-                        StatsLog.write_non_chained(StatsLog.GPS_SCAN_STATE_CHANGED, getUid(), null,
-                                0);
-                    } else {
-                        StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null,
-                                sensor, 0);
-                    }
+                if (sensor != Sensor.GPS) {
+                    StatsLog.write_non_chained(StatsLog.SENSOR_STATE_CHANGED, getUid(), null,
+                             sensor, 0);
                 }
             }
         }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 079ba0b..f9a2341 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -53,6 +53,8 @@
     public static final int DISABLE_VERIFIER = 1 << 9;
     /** Only use oat files located in /system. Otherwise use dex/jar/apk . */
     public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10;
+    /** Do not enfore hidden API access restrictions. */
+    public static final int DISABLE_HIDDEN_API_CHECKS = 1 << 11;
 
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
@@ -156,6 +158,9 @@
      */
     public static int forkSystemServer(int uid, int gid, int[] gids, int runtimeFlags,
             int[][] rlimits, long permittedCapabilities, long effectiveCapabilities) {
+        // SystemServer is always allowed to use hidden APIs.
+        runtimeFlags |= DISABLE_HIDDEN_API_CHECKS;
+
         VM_HOOKS.preFork();
         // Resets nice priority for zygote process.
         resetNicePriority();
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 28291ae..e08caa8 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -23,6 +23,7 @@
 import android.annotation.Nullable;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.LocaleList;
 import android.os.Looper;
 import android.os.Message;
 import android.os.RemoteException;
@@ -64,6 +65,7 @@
     private static final int DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO = 140;
     private static final int DO_CLOSE_CONNECTION = 150;
     private static final int DO_COMMIT_CONTENT = 160;
+    private static final int DO_REPORT_LANGUAGE_HINT = 170;
 
     @GuardedBy("mLock")
     @Nullable
@@ -217,6 +219,10 @@
                 callback));
     }
 
+    public void reportLanguageHint(@NonNull LocaleList languageHint) {
+        dispatchMessage(obtainMessageO(DO_REPORT_LANGUAGE_HINT, languageHint));
+    }
+
     void dispatchMessage(Message msg) {
         // If we are calling this from the main thread, then we can call
         // right through.  Otherwise, we need to send the message to the
@@ -577,6 +583,16 @@
                 }
                 return;
             }
+            case DO_REPORT_LANGUAGE_HINT: {
+                final LocaleList languageHint = (LocaleList) msg.obj;
+                final InputConnection ic = getInputConnection();
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "reportLanguageHint on inactive InputConnection");
+                    return;
+                }
+                ic.reportLanguageHint(languageHint);
+                return;
+            }
         }
         Log.w(TAG, "Unhandled message code: " + msg.what);
     }
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index c227991..e69a87ff 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -17,6 +17,7 @@
 package com.android.internal.view;
 
 import android.os.Bundle;
+import android.os.LocaleList;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
@@ -78,4 +79,6 @@
 
     void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, int sec,
             IInputContextCallback callback);
+
+    void reportLanguageHint(in LocaleList languageHint);
 }
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 5b65bbe..34be598 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -22,6 +22,7 @@
 import android.inputmethodservice.AbstractInputMethodService;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.LocaleList;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
@@ -620,6 +621,14 @@
     }
 
     @AnyThread
+    public void reportLanguageHint(@NonNull LocaleList languageHint) {
+        try {
+            mIInputContext.reportLanguageHint(languageHint);
+        } catch (RemoteException e) {
+        }
+    }
+
+    @AnyThread
     private boolean isMethodMissing(@MissingMethodFlags final int methodFlag) {
         return (mMissingMethods & methodFlag) == methodFlag;
     }
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index 6af41a5..324f923 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -256,7 +256,7 @@
             final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity,
                     mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
             if (hgrav == Gravity.RIGHT) {
-                xOffset += mAnchorView.getWidth();
+                xOffset -= mAnchorView.getWidth();
             }
 
             popup.setHorizontalOffset(xOffset);
diff --git a/core/java/com/android/internal/view/menu/StandardMenuPopup.java b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
index d9ca5be..445379b 100644
--- a/core/java/com/android/internal/view/menu/StandardMenuPopup.java
+++ b/core/java/com/android/internal/view/menu/StandardMenuPopup.java
@@ -263,7 +263,6 @@
                     mShownAnchorView, mOverflowOnly, mPopupStyleAttr, mPopupStyleRes);
             subPopup.setPresenterCallback(mPresenterCallback);
             subPopup.setForceShowIcon(MenuPopup.shouldPreserveIconSpacing(subMenu));
-            subPopup.setGravity(mDropDownGravity);
 
             // Pass responsibility for handling onDismiss to the submenu.
             subPopup.setOnDismissListener(mOnDismissListener);
@@ -273,8 +272,17 @@
             mMenu.close(false /* closeAllMenus */);
 
             // Show the new sub-menu popup at the same location as this popup.
-            final int horizontalOffset = mPopup.getHorizontalOffset();
+            int horizontalOffset = mPopup.getHorizontalOffset();
             final int verticalOffset = mPopup.getVerticalOffset();
+
+            // As xOffset of parent menu popup is subtracted with Anchor width for Gravity.RIGHT,
+            // So, again to display sub-menu popup in same xOffset, add the Anchor width.
+            final int hgrav = Gravity.getAbsoluteGravity(mDropDownGravity,
+                mAnchorView.getLayoutDirection()) & Gravity.HORIZONTAL_GRAVITY_MASK;
+            if (hgrav == Gravity.RIGHT) {
+              horizontalOffset += mAnchorView.getWidth();
+            }
+
             if (subPopup.tryShow(horizontalOffset, verticalOffset)) {
                 if (mPresenterCallback != null) {
                     mPresenterCallback.onOpenSubMenu(subMenu);
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 47765d9..102ff95 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",
@@ -189,6 +189,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 aa9a824..e3b5c8f 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -122,6 +122,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);
@@ -1340,6 +1341,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/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
index 8b3ce66..262b553 100644
--- a/core/jni/android/graphics/AnimatedImageDrawable.cpp
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -72,7 +72,6 @@
     }
 
     sk_sp<AnimatedImageDrawable> drawable(new AnimatedImageDrawable(animatedImg));
-    drawable->start();
     return reinterpret_cast<jlong>(drawable.release());
 }
 
@@ -114,9 +113,9 @@
     return drawable->isRunning();
 }
 
-static void AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+static jboolean AnimatedImageDrawable_nStart(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
     auto* drawable = reinterpret_cast<AnimatedImageDrawable*>(nativePtr);
-    drawable->start();
+    return drawable->start();
 }
 
 static void AnimatedImageDrawable_nStop(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
@@ -138,7 +137,7 @@
     { "nGetAlpha",           "(J)I",                                                         (void*) AnimatedImageDrawable_nGetAlpha },
     { "nSetColorFilter",     "(JJ)V",                                                        (void*) AnimatedImageDrawable_nSetColorFilter },
     { "nIsRunning",          "(J)Z",                                                         (void*) AnimatedImageDrawable_nIsRunning },
-    { "nStart",              "(J)V",                                                         (void*) AnimatedImageDrawable_nStart },
+    { "nStart",              "(J)Z",                                                         (void*) AnimatedImageDrawable_nStart },
     { "nStop",               "(J)V",                                                         (void*) AnimatedImageDrawable_nStop },
     { "nNativeByteSize",     "(J)J",                                                         (void*) AnimatedImageDrawable_nNativeByteSize },
 };
diff --git a/core/jni/android/graphics/FontFamily.cpp b/core/jni/android/graphics/FontFamily.cpp
index dd3e6f0..c50026e 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"
 
@@ -224,7 +224,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;
@@ -236,27 +237,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_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 c6828c4..403937b 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1,1846 +1,1437 @@
-/* //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.
+      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();
+        }
+      }
 
-                // 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::OVERLAY_DIR, &st) == 0) {
+          argv[argc++] = AssetManager::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;
-    }
+      // 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 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));
 }
 
 // ----------------------------------------------------------------------------
 
-// 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;
-        }
-        env->SetObjectArrayElement(array, i, str);
-        env->DeleteLocalRef(str);
-    }
-
-    delete dir;
-
-    return array;
-}
-
-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();
-}
-
-// ----------------------------------------------------------------------------
-
-/*
- * 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/providers/settings.proto b/core/proto/android/providers/settings.proto
index fd28322..27fbb24 100644
--- a/core/proto/android/providers/settings.proto
+++ b/core/proto/android/providers/settings.proto
@@ -394,8 +394,9 @@
     optional SettingProto wifi_connected_mac_randomization_enabled = 350;
     optional SettingProto show_restart_in_crash_dialog = 351;
     optional SettingProto show_mute_in_crash_dialog = 352;
+    optional SettingProto chained_battery_attribution_enabled = 353;
 
-    // Next tag = 353;
+    // Next tag = 354;
 }
 
 message SecureSettingsProto {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index ea791a5..0861e710 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -971,6 +971,23 @@
                 android:description="@string/permdesc_manageOwnCalls"
                 android:protectionLevel="normal" />
 
+    <!-- Allows a calling app to continue a call which was started in another app.  An example is a
+         video calling app that wants to continue a voice call on the user's mobile network.<p>
+         When the handover of a call from one app to another takes place, there are two devices
+         which are involved in the handover; the initiating and receiving devices.  The initiating
+         device is where the request to handover the call was started, and the receiving device is
+         where the handover request is confirmed by the other party.<p>
+         This permission protects access to the
+         {@link android.telecom.TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)} which
+         the receiving side of the handover uses to accept a handover.
+         <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.ACCEPT_HANDOVER"
+                android:permissionGroup="android.permission-group.PHONE"
+                android.label="@string/permlab_acceptHandover"
+                android:description="@string/permdesc_acceptHandovers"
+                android:protectionLevel="dangerous" />
+
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device microphone                        -->
     <!-- ====================================================================== -->
diff --git a/core/res/res/values/ids.xml b/core/res/res/values/ids.xml
index b40117e..c90a0df 100644
--- a/core/res/res/values/ids.xml
+++ b/core/res/res/values/ids.xml
@@ -163,4 +163,9 @@
   <!-- Action used to manually trigger an autofill request -->
   <item type="id" name="autofill" />
 
+    <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_SHOW_TOOLTIP}. -->
+    <item type="id" name="accessibilityActionShowTooltip" />
+
+    <!-- Accessibility action identifier for {@link android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction#ACTION_HIDE_TOOLTIP}. -->
+    <item type="id" name="accessibilityActionHideTooltip" />
 </resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 9cdf553..80fc5db 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2874,6 +2874,8 @@
     </public-group>
 
     <public-group type="id" first-id="0x01020044">
+      <public name="accessibilityActionShowTooltip" />
+      <public name="accessibilityActionHideTooltip" />
     </public-group>
 
     <public-group type="string" first-id="0x0104001b">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 71e963a..012212f3 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1133,6 +1133,17 @@
     <string name="permdesc_manageOwnCalls">Allows the app to route its calls through the system in
         order to improve the calling experience.</string>
 
+    <!-- Title of an application permission.  When granted the user is giving access to a third
+         party app to continue a call which originated in another app.  For example, the user
+         could be in a voice call over their carrier's mobile network, and a third party video
+         calling app wants to continue that voice call as a video call. -->
+    <string name="permlab_acceptHandover">continue a call from another app</string>
+    <!-- Description of an application permission.  When granted the user is giving access to a
+         third party app to continue a call which originated in another app.  For example, the user
+         could be in a voice call over their carrier's mobile network, and a third party video
+         calling app wants to continue that voice call as a video call -->
+    <string name="permdesc_acceptHandovers">Allows the app to continue a call which was started in another app.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_readPhoneNumbers">read phone numbers</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -3027,6 +3038,8 @@
 
     <!-- Notification title for a nearby open wireless network.-->
     <string name="wifi_available_title">Connect to open Wi\u2011Fi network</string>
+    <!-- Notification title for a nearby carrier wireless network.-->
+    <string name="wifi_available_carrier_network_title">Connect to carrier Wi\u2011Fi network</string>
     <!-- Notification title when the system is connecting to the specified open network. The network name is specified in the notification content. -->
     <string name="wifi_available_title_connecting">Connecting to open Wi\u2011Fi network</string>
     <!-- Notification title when the system has connected to the open network. The network name is specified in the notification content. -->
@@ -4621,6 +4634,11 @@
     <!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
     <string name="work_mode_turn_on">Turn on</string>
 
+    <!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
+    <string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
+    <!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] -->
+    <string name="deprecated_target_sdk_app_store">Check for update</string>
+
     <!-- Notification title shown when new SMS/MMS is received while the device is locked [CHAR LIMIT=NONE] -->
     <string name="new_sms_notification_title">You have new messages</string>
     <!-- Notification content shown when new SMS/MMS is received while the device is locked [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index ee20873..f94168d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1912,6 +1912,7 @@
   <java-symbol type="plurals" name="wifi_available" />
   <java-symbol type="plurals" name="wifi_available_detailed" />
   <java-symbol type="string" name="wifi_available_title" />
+  <java-symbol type="string" name="wifi_available_carrier_network_title" />
   <java-symbol type="string" name="wifi_available_title_connecting" />
   <java-symbol type="string" name="wifi_available_title_connected" />
   <java-symbol type="string" name="wifi_available_title_failed_to_connect" />
@@ -2702,6 +2703,9 @@
   <java-symbol type="string" name="work_mode_off_message" />
   <java-symbol type="string" name="work_mode_turn_on" />
 
+  <java-symbol type="string" name="deprecated_target_sdk_message" />
+  <java-symbol type="string" name="deprecated_target_sdk_app_store" />
+
   <!-- New SMS notification while phone is locked. -->
   <java-symbol type="string" name="new_sms_notification_title" />
   <java-symbol type="string" name="new_sms_notification_content" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3e38010..53c22f6 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1045,6 +1045,13 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.view.menu.ContextMenuActivity" android:label="ContextMenu" android:theme="@android:style/Theme.Material">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="android.view.menu.MenuWith1Item" android:label="MenuWith1Item">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/core/tests/coretests/res/layout/context_menu.xml b/core/tests/coretests/res/layout/context_menu.xml
new file mode 100644
index 0000000..3b9e2bd
--- /dev/null
+++ b/core/tests/coretests/res/layout/context_menu.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 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.
+*/
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <LinearLayout
+            android:id="@+id/context_menu_target_ltr"
+            android:orientation="horizontal"
+            android:layoutDirection="ltr"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="50px"
+            android:layout_marginEnd="50px">
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="LTR"/>
+    </LinearLayout>
+
+    <LinearLayout
+            android:id="@+id/context_menu_target_rtl"
+            android:orientation="horizontal"
+            android:layoutDirection="rtl"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="50px"
+            android:layout_marginEnd="50px">
+        <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="RTL"/>
+    </LinearLayout>
+
+</LinearLayout>
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
index a427a2f..952a64d 100644
--- a/core/tests/coretests/src/android/os/WorkSourceTest.java
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -349,6 +349,15 @@
         assertEquals(100, wc.getAttributionUid());
     }
 
+    public void testGetAttributionTag() {
+        WorkSource ws1 = new WorkSource();
+        WorkChain wc = ws1.createWorkChain();
+        wc.addNode(100, "tag");
+        assertEquals("tag", wc.getAttributionTag());
+        wc.addNode(200, "tag2");
+        assertEquals("tag", wc.getAttributionTag());
+    }
+
     public void testRemove_fromChainedWorkSource() {
         WorkSource ws1 = new WorkSource();
         ws1.createWorkChain().addNode(50, "foo");
@@ -368,4 +377,25 @@
         assertEquals(1, ws1.getWorkChains().size());
         assertEquals(75, ws1.getWorkChains().get(0).getAttributionUid());
     }
+
+    public void testTransferWorkChains() {
+        WorkSource ws1 = new WorkSource();
+        WorkChain wc1 = ws1.createWorkChain().addNode(100, "tag");
+        WorkChain wc2 = ws1.createWorkChain().addNode(200, "tag2");
+
+        WorkSource ws2 = new WorkSource();
+        ws2.transferWorkChains(ws1);
+
+        assertEquals(0, ws1.getWorkChains().size());
+        assertEquals(2, ws2.getWorkChains().size());
+        assertSame(wc1, ws2.getWorkChains().get(0));
+        assertSame(wc2, ws2.getWorkChains().get(1));
+
+        ws1.clear();
+        ws1.createWorkChain().addNode(300, "tag3");
+        ws1.transferWorkChains(ws2);
+        assertEquals(0, ws2.getWorkChains().size());
+        assertSame(wc1, ws1.getWorkChains().get(0));
+        assertSame(wc2, ws1.getWorkChains().get(1));
+    }
 }
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 08d023d..1520fb6 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -44,6 +44,12 @@
 public class SettingsBackupTest {
 
     /**
+     * see {@link com.google.android.systemui.power.EnhancedEstimatesGoogleImpl} for more details
+     */
+    public static final String HYBRID_SYSUI_BATTERY_WARNING_FLAGS =
+            "hybrid_sysui_battery_warning_flags";
+
+    /**
      * The following blacklists contain settings that should *not* be backed up and restored to
      * another device.  As a general rule, anything that is not user configurable should be
      * blacklisted (and conversely, things that *are* user configurable *should* be backed up)
@@ -114,6 +120,12 @@
                     Settings.Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
                     Settings.Global.BATTERY_STATS_CONSTANTS,
                     Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE,
+                    Settings.Global.BLE_SCAN_LOW_POWER_WINDOW_MS,
+                    Settings.Global.BLE_SCAN_LOW_POWER_INTERVAL_MS,
+                    Settings.Global.BLE_SCAN_BALANCED_WINDOW_MS,
+                    Settings.Global.BLE_SCAN_BALANCED_INTERVAL_MS,
+                    Settings.Global.BLE_SCAN_LOW_LATENCY_WINDOW_MS,
+                    Settings.Global.BLE_SCAN_LOW_LATENCY_INTERVAL_MS,
                     Settings.Global.BLUETOOTH_A2DP_SINK_PRIORITY_PREFIX,
                     Settings.Global.BLUETOOTH_A2DP_SRC_PRIORITY_PREFIX,
                     Settings.Global.BLUETOOTH_A2DP_SUPPORTS_OPTIONAL_CODECS_PREFIX,
@@ -226,6 +238,7 @@
                     Settings.Global.HDMI_SYSTEM_AUDIO_CONTROL_ENABLED,
                     Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
                     Settings.Global.HTTP_PROXY,
+                    HYBRID_SYSUI_BATTERY_WARNING_FLAGS,
                     Settings.Global.INET_CONDITION_DEBOUNCE_DOWN_DELAY,
                     Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY,
                     Settings.Global.INSTANT_APP_DEXOPT_ENABLED,
@@ -431,7 +444,8 @@
                     Settings.Global.ZEN_MODE_CONFIG_ETAG,
                     Settings.Global.ZEN_MODE_RINGER_LEVEL,
                     Settings.Global.ZRAM_ENABLED,
-                    Settings.Global.OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION);
+                    Settings.Global.OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION,
+                    Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED);
 
     private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
              newHashSet(
diff --git a/core/tests/coretests/src/android/util/PollingCheck.java b/core/tests/coretests/src/android/util/PollingCheck.java
new file mode 100644
index 0000000..468b9b2
--- /dev/null
+++ b/core/tests/coretests/src/android/util/PollingCheck.java
@@ -0,0 +1,104 @@
+/*
+ * 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.util;
+
+import org.junit.Assert;
+
+/**
+ * Utility used for testing that allows to poll for a certain condition to happen within a timeout.
+ *
+ * Code copied from com.android.compatibility.common.util.PollingCheck
+ */
+public abstract class PollingCheck {
+
+    private static final long DEFAULT_TIMEOUT = 3000;
+    private static final long TIME_SLICE = 50;
+    private final long mTimeout;
+
+    /**
+     * The condition that the PollingCheck should use to proceed successfully.
+     */
+    public interface PollingCheckCondition {
+
+        /**
+         * @return Whether the polling condition has been met.
+         */
+        boolean canProceed();
+    }
+
+    public PollingCheck(long timeout) {
+        mTimeout = timeout;
+    }
+
+    protected abstract boolean check();
+
+    /**
+     * Start running the polling check.
+     */
+    public void run() {
+        if (check()) {
+            return;
+        }
+
+        long timeout = mTimeout;
+        while (timeout > 0) {
+            try {
+                Thread.sleep(TIME_SLICE);
+            } catch (InterruptedException e) {
+                Assert.fail("unexpected InterruptedException");
+            }
+
+            if (check()) {
+                return;
+            }
+
+            timeout -= TIME_SLICE;
+        }
+
+        Assert.fail("unexpected timeout");
+    }
+
+    /**
+     * Instantiate and start polling for a given condition with a default 3000ms timeout.
+     *
+     * @param condition The condition to check for success.
+     */
+    public static void waitFor(final PollingCheckCondition condition) {
+        new PollingCheck(DEFAULT_TIMEOUT) {
+            @Override
+            protected boolean check() {
+                return condition.canProceed();
+            }
+        }.run();
+    }
+
+    /**
+     * Instantiate and start polling for a given condition.
+     *
+     * @param timeout Time out in ms
+     * @param condition The condition to check for success.
+     */
+    public static void waitFor(long timeout, final PollingCheckCondition condition) {
+        new PollingCheck(timeout) {
+            @Override
+            protected boolean check() {
+                return condition.canProceed();
+            }
+        }.run();
+    }
+}
+
diff --git a/core/tests/coretests/src/android/view/menu/ContextMenuActivity.java b/core/tests/coretests/src/android/view/menu/ContextMenuActivity.java
new file mode 100644
index 0000000..830b3d5
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/ContextMenuActivity.java
@@ -0,0 +1,54 @@
+/*
+ * 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 android.view.menu;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View;
+
+import com.android.frameworks.coretests.R;
+
+public class ContextMenuActivity extends Activity {
+
+    static final String LABEL_ITEM = "Item";
+    static final String LABEL_SUBMENU = "Submenu";
+    static final String LABEL_SUBITEM = "Subitem";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.context_menu);
+        registerForContextMenu(getTargetLtr());
+        registerForContextMenu(getTargetRtl());
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+        menu.add(LABEL_ITEM);
+        menu.addSubMenu(LABEL_SUBMENU).add(LABEL_SUBITEM);
+    }
+
+    View getTargetLtr() {
+        return findViewById(R.id.context_menu_target_ltr);
+    }
+
+    View getTargetRtl() {
+        return findViewById(R.id.context_menu_target_rtl);
+    }
+}
diff --git a/core/tests/coretests/src/android/view/menu/ContextMenuTest.java b/core/tests/coretests/src/android/view/menu/ContextMenuTest.java
new file mode 100644
index 0000000..59d4e55
--- /dev/null
+++ b/core/tests/coretests/src/android/view/menu/ContextMenuTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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 android.view.menu;
+
+import android.content.Context;
+import android.graphics.Point;
+import android.support.test.filters.MediumTest;
+import android.test.ActivityInstrumentationTestCase;
+import android.util.PollingCheck;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.espresso.ContextMenuUtils;
+
+@MediumTest
+public class ContextMenuTest extends ActivityInstrumentationTestCase<ContextMenuActivity> {
+
+    public ContextMenuTest() {
+        super("com.android.frameworks.coretests", ContextMenuActivity.class);
+    }
+
+    public void testContextMenuPositionLtr() throws InterruptedException {
+        testMenuPosition(getActivity().getTargetLtr());
+    }
+
+    public void testContextMenuPositionRtl() throws InterruptedException {
+        testMenuPosition(getActivity().getTargetRtl());
+    }
+
+    private void testMenuPosition(View target) throws InterruptedException {
+        final int minScreenDimension = getMinScreenDimension();
+        if (minScreenDimension < 320) {
+            // Assume there is insufficient room for the context menu to be aligned properly.
+            return;
+        }
+
+        int offsetX = target.getWidth() / 2;
+        int offsetY = target.getHeight() / 2;
+
+        getInstrumentation().runOnMainSync(() -> target.performLongClick(offsetX, offsetY));
+
+        PollingCheck.waitFor(
+                () -> ContextMenuUtils.isMenuItemClickable(ContextMenuActivity.LABEL_SUBMENU));
+
+        ContextMenuUtils.assertContextMenuAlignment(target, offsetX, offsetY);
+
+        ContextMenuUtils.clickMenuItem(ContextMenuActivity.LABEL_SUBMENU);
+
+        PollingCheck.waitFor(
+                () -> ContextMenuUtils.isMenuItemClickable(ContextMenuActivity.LABEL_SUBITEM));
+
+        if (minScreenDimension < getCascadingMenuTreshold()) {
+            // A non-cascading submenu should be displayed at the same location as its parent.
+            // Not testing cascading submenu position, as it is positioned differently.
+            ContextMenuUtils.assertContextMenuAlignment(target, offsetX, offsetY);
+        }
+    }
+
+    /**
+     * Returns the minimum of the default display's width and height.
+     */
+    private int getMinScreenDimension() {
+        final WindowManager windowManager = (WindowManager) getActivity().getSystemService(
+                Context.WINDOW_SERVICE);
+        final Display display = windowManager.getDefaultDisplay();
+        final Point displaySize = new Point();
+        display.getRealSize(displaySize);
+        return Math.min(displaySize.x, displaySize.y);
+    }
+
+    /**
+     * Returns the minimum display size where cascading submenus are supported.
+     */
+    private int getCascadingMenuTreshold() {
+        // Use the same dimension resource as in MenuPopupHelper.createPopup().
+        return getActivity().getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.cascading_menus_min_smallest_width);
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/espresso/ContextMenuUtils.java b/core/tests/coretests/src/android/widget/espresso/ContextMenuUtils.java
index c8218aa..02ee9be 100644
--- a/core/tests/coretests/src/android/widget/espresso/ContextMenuUtils.java
+++ b/core/tests/coretests/src/android/widget/espresso/ContextMenuUtils.java
@@ -17,25 +17,33 @@
 package android.widget.espresso;
 
 import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
 import static android.support.test.espresso.assertion.ViewAssertions.matches;
 import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
 import static android.support.test.espresso.matcher.ViewMatchers.hasDescendant;
 import static android.support.test.espresso.matcher.ViewMatchers.hasFocus;
 import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
 import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
 import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
 import static android.support.test.espresso.matcher.ViewMatchers.withText;
+
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.not;
 
-import com.android.internal.view.menu.ListMenuItemView;
-
 import android.support.test.espresso.NoMatchingRootException;
 import android.support.test.espresso.NoMatchingViewException;
 import android.support.test.espresso.ViewInteraction;
 import android.support.test.espresso.matcher.ViewMatchers;
+import android.view.View;
 import android.widget.MenuPopupWindow.MenuDropDownListView;
 
+import com.android.internal.view.menu.ListMenuItemView;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
 /**
  * Espresso utility methods for the context menu.
  */
@@ -82,10 +90,15 @@
     private static void asssertContextMenuContainsItemWithEnabledState(String itemLabel,
             boolean enabled) {
         onContextMenu().check(matches(
-                hasDescendant(allOf(
-                        isAssignableFrom(ListMenuItemView.class),
-                        enabled ? isEnabled() : not(isEnabled()),
-                        hasDescendant(withText(itemLabel))))));
+                hasDescendant(getVisibleMenuItemMatcher(itemLabel, enabled))));
+    }
+
+    private static Matcher<View> getVisibleMenuItemMatcher(String itemLabel, boolean enabled) {
+        return allOf(
+                isAssignableFrom(ListMenuItemView.class),
+                hasDescendant(withText(itemLabel)),
+                enabled ? isEnabled() : not(isEnabled()),
+                isDisplayingAtLeast(90));
     }
 
     /**
@@ -107,4 +120,70 @@
     public static void assertContextMenuContainsItemDisabled(String itemLabel) {
         asssertContextMenuContainsItemWithEnabledState(itemLabel, false);
     }
+
+    /**
+     * Asserts that the context menu window is aligned to a given view with a given offset.
+     *
+     * @param anchor Anchor view.
+     * @param offsetX x offset
+     * @param offsetY y offset.
+     * @throws AssertionError if the assertion fails
+     */
+    public static void assertContextMenuAlignment(View anchor, int offsetX, int offsetY) {
+        int [] expectedLocation = new int[2];
+        anchor.getLocationOnScreen(expectedLocation);
+        expectedLocation[0] += offsetX;
+        expectedLocation[1] += offsetY;
+
+        final boolean rtl = anchor.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+
+        onContextMenu().check(matches(new TypeSafeMatcher<View>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("root view ");
+                description.appendText(rtl ? "right" : "left");
+                description.appendText("=");
+                description.appendText(Integer.toString(offsetX));
+                description.appendText(", top=");
+                description.appendText(Integer.toString(offsetY));
+            }
+
+            @Override
+            public boolean matchesSafely(View view) {
+                View rootView = view.getRootView();
+                int [] actualLocation = new int[2];
+                rootView.getLocationOnScreen(actualLocation);
+                if (rtl) {
+                    actualLocation[0] += rootView.getWidth();
+                }
+                return expectedLocation[0] == actualLocation[0]
+                    && expectedLocation[1] == actualLocation[1];
+            }
+        }));
+    }
+
+    /**
+     * Check is the menu item is clickable (i.e. visible and enabled).
+     *
+     * @param itemLabel Label of the item.
+     * @return True if the menu item is clickable.
+     */
+    public static boolean isMenuItemClickable(String itemLabel) {
+        try {
+            onContextMenu().check(matches(
+                    hasDescendant(getVisibleMenuItemMatcher(itemLabel, true))));
+            return true;
+        } catch (NoMatchingRootException | NoMatchingViewException | AssertionError e) {
+            return false;
+        }
+    }
+
+    /**
+     * Click on a menu item with the specified label
+     * @param itemLabel Label of the item.
+     */
+    public static void clickMenuItem(String itemLabel) {
+        onView(getVisibleMenuItemMatcher(itemLabel, true))
+                .inRoot(withDecorView(hasFocus())).perform(click());
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index a55563a..82ac9da 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -23,10 +23,12 @@
 import android.os.BatteryManager;
 import android.os.BatteryStats;
 import android.os.BatteryStats.HistoryItem;
+import android.os.BatteryStats.Uid.Sensor;
 import android.os.WorkSource;
 import android.support.test.filters.SmallTest;
 import android.view.Display;
 
+import com.android.internal.os.BatteryStatsImpl.DualTimer;
 import com.android.internal.os.BatteryStatsImpl.Uid;
 import junit.framework.TestCase;
 
@@ -446,4 +448,52 @@
         pkg = bi.getPackageStatsLocked(100, "com.foo.baz_alternate");
         assertEquals(0, pkg.getWakeupAlarmStats().size());
     }
+
+    @SmallTest
+    public void testNoteGpsChanged() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setRecordAllHistoryLocked(true);
+        bi.forceRecordAllHistory();
+        bi.mForceOnBattery = true;
+
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+        WorkSource ws = new WorkSource();
+        ws.add(UID);
+
+        bi.noteGpsChangedLocked(new WorkSource(), ws);
+        DualTimer t = bi.getUidStatsLocked(UID).getSensorTimerLocked(Sensor.GPS, false);
+        assertNotNull(t);
+        assertTrue(t.isRunningLocked());
+
+        bi.noteGpsChangedLocked(ws, new WorkSource());
+        t = bi.getUidStatsLocked(UID).getSensorTimerLocked(Sensor.GPS, false);
+        assertFalse(t.isRunningLocked());
+    }
+
+    @SmallTest
+    public void testNoteGpsChanged_workSource() {
+        final MockClocks clocks = new MockClocks(); // holds realtime and uptime in ms
+        MockBatteryStatsImpl bi = new MockBatteryStatsImpl(clocks);
+        bi.setRecordAllHistoryLocked(true);
+        bi.forceRecordAllHistory();
+        bi.mForceOnBattery = true;
+
+        bi.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        bi.noteUidProcessStateLocked(UID, ActivityManager.PROCESS_STATE_TOP);
+
+        WorkSource ws = new WorkSource();
+        ws.createWorkChain().addNode(UID, "com.foo");
+
+        bi.noteGpsChangedLocked(new WorkSource(), ws);
+        DualTimer t = bi.getUidStatsLocked(UID).getSensorTimerLocked(Sensor.GPS, false);
+        assertNotNull(t);
+        assertTrue(t.isRunningLocked());
+
+        bi.noteGpsChangedLocked(ws, new WorkSource());
+        t = bi.getUidStatsLocked(UID).getSensorTimerLocked(Sensor.GPS, false);
+        assertFalse(t.isRunningLocked());
+    }
 }
diff --git a/core/tests/overlaytests/device/AndroidManifest.xml b/core/tests/overlaytests/device/AndroidManifest.xml
new file mode 100644
index 0000000..e01caee
--- /dev/null
+++ b/core/tests/overlaytests/device/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.server.om.test">
+
+    <uses-sdk android:minSdkVersion="21" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.server.om.test" />
+
+</manifest>
diff --git a/core/tests/overlaytests/OverlayAppFiltered/Android.mk b/core/tests/overlaytests/device/OverlayAppFiltered/Android.mk
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFiltered/Android.mk
rename to core/tests/overlaytests/device/OverlayAppFiltered/Android.mk
diff --git a/core/tests/overlaytests/OverlayAppFiltered/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppFiltered/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFiltered/AndroidManifest.xml
rename to core/tests/overlaytests/device/OverlayAppFiltered/AndroidManifest.xml
diff --git a/core/tests/overlaytests/OverlayAppFiltered/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayAppFiltered/res/raw/lorem_ipsum.txt
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFiltered/res/raw/lorem_ipsum.txt
rename to core/tests/overlaytests/device/OverlayAppFiltered/res/raw/lorem_ipsum.txt
diff --git a/core/tests/overlaytests/OverlayAppFiltered/res/values/config.xml b/core/tests/overlaytests/device/OverlayAppFiltered/res/values/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFiltered/res/values/config.xml
rename to core/tests/overlaytests/device/OverlayAppFiltered/res/values/config.xml
diff --git a/core/tests/overlaytests/OverlayAppFiltered/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayAppFiltered/res/xml/integer.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFiltered/res/xml/integer.xml
rename to core/tests/overlaytests/device/OverlayAppFiltered/res/xml/integer.xml
diff --git a/core/tests/overlaytests/OverlayAppFirst/Android.mk b/core/tests/overlaytests/device/OverlayAppFirst/Android.mk
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/Android.mk
rename to core/tests/overlaytests/device/OverlayAppFirst/Android.mk
diff --git a/core/tests/overlaytests/OverlayAppFirst/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppFirst/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/AndroidManifest.xml
rename to core/tests/overlaytests/device/OverlayAppFirst/AndroidManifest.xml
diff --git a/core/tests/overlaytests/OverlayAppFirst/res/drawable-nodpi/drawable.jpg b/core/tests/overlaytests/device/OverlayAppFirst/res/drawable-nodpi/drawable.jpg
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/res/drawable-nodpi/drawable.jpg
rename to core/tests/overlaytests/device/OverlayAppFirst/res/drawable-nodpi/drawable.jpg
Binary files differ
diff --git a/core/tests/overlaytests/OverlayAppFirst/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayAppFirst/res/raw/lorem_ipsum.txt
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/res/raw/lorem_ipsum.txt
rename to core/tests/overlaytests/device/OverlayAppFirst/res/raw/lorem_ipsum.txt
diff --git a/core/tests/overlaytests/OverlayAppFirst/res/values-sv/config.xml b/core/tests/overlaytests/device/OverlayAppFirst/res/values-sv/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/res/values-sv/config.xml
rename to core/tests/overlaytests/device/OverlayAppFirst/res/values-sv/config.xml
diff --git a/core/tests/overlaytests/OverlayAppFirst/res/values/config.xml b/core/tests/overlaytests/device/OverlayAppFirst/res/values/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/res/values/config.xml
rename to core/tests/overlaytests/device/OverlayAppFirst/res/values/config.xml
diff --git a/core/tests/overlaytests/OverlayAppFirst/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayAppFirst/res/xml/integer.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppFirst/res/xml/integer.xml
rename to core/tests/overlaytests/device/OverlayAppFirst/res/xml/integer.xml
diff --git a/core/tests/overlaytests/OverlayAppSecond/Android.mk b/core/tests/overlaytests/device/OverlayAppSecond/Android.mk
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/Android.mk
rename to core/tests/overlaytests/device/OverlayAppSecond/Android.mk
diff --git a/core/tests/overlaytests/OverlayAppSecond/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayAppSecond/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/AndroidManifest.xml
rename to core/tests/overlaytests/device/OverlayAppSecond/AndroidManifest.xml
diff --git a/core/tests/overlaytests/OverlayAppSecond/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayAppSecond/res/raw/lorem_ipsum.txt
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/res/raw/lorem_ipsum.txt
rename to core/tests/overlaytests/device/OverlayAppSecond/res/raw/lorem_ipsum.txt
diff --git a/core/tests/overlaytests/OverlayAppSecond/res/values-sv/config.xml b/core/tests/overlaytests/device/OverlayAppSecond/res/values-sv/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/res/values-sv/config.xml
rename to core/tests/overlaytests/device/OverlayAppSecond/res/values-sv/config.xml
diff --git a/core/tests/overlaytests/OverlayAppSecond/res/values/config.xml b/core/tests/overlaytests/device/OverlayAppSecond/res/values/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/res/values/config.xml
rename to core/tests/overlaytests/device/OverlayAppSecond/res/values/config.xml
diff --git a/core/tests/overlaytests/OverlayAppSecond/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayAppSecond/res/xml/integer.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayAppSecond/res/xml/integer.xml
rename to core/tests/overlaytests/device/OverlayAppSecond/res/xml/integer.xml
diff --git a/core/tests/overlaytests/OverlayTest/Android.mk b/core/tests/overlaytests/device/OverlayTest/Android.mk
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/Android.mk
rename to core/tests/overlaytests/device/OverlayTest/Android.mk
diff --git a/core/tests/overlaytests/OverlayTest/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayTest/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/AndroidManifest.xml
rename to core/tests/overlaytests/device/OverlayTest/AndroidManifest.xml
diff --git a/core/tests/overlaytests/OverlayTest/res/drawable-nodpi/drawable.jpg b/core/tests/overlaytests/device/OverlayTest/res/drawable-nodpi/drawable.jpg
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/res/drawable-nodpi/drawable.jpg
rename to core/tests/overlaytests/device/OverlayTest/res/drawable-nodpi/drawable.jpg
Binary files differ
diff --git a/core/tests/overlaytests/OverlayTest/res/raw/lorem_ipsum.txt b/core/tests/overlaytests/device/OverlayTest/res/raw/lorem_ipsum.txt
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/res/raw/lorem_ipsum.txt
rename to core/tests/overlaytests/device/OverlayTest/res/raw/lorem_ipsum.txt
diff --git a/core/tests/overlaytests/OverlayTest/res/values-sv/config.xml b/core/tests/overlaytests/device/OverlayTest/res/values-sv/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/res/values-sv/config.xml
rename to core/tests/overlaytests/device/OverlayTest/res/values-sv/config.xml
diff --git a/core/tests/overlaytests/OverlayTest/res/values/config.xml b/core/tests/overlaytests/device/OverlayTest/res/values/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/res/values/config.xml
rename to core/tests/overlaytests/device/OverlayTest/res/values/config.xml
diff --git a/core/tests/overlaytests/OverlayTest/res/xml/integer.xml b/core/tests/overlaytests/device/OverlayTest/res/xml/integer.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/res/xml/integer.xml
rename to core/tests/overlaytests/device/OverlayTest/res/xml/integer.xml
diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java
rename to core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/OverlayBaseTest.java
diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java
rename to core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithMultipleOverlaysTest.java
diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java
rename to core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithOverlayTest.java
diff --git a/core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java
similarity index 100%
rename from core/tests/overlaytests/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java
rename to core/tests/overlaytests/device/OverlayTest/src/com/android/overlaytest/WithoutOverlayTest.java
diff --git a/core/tests/overlaytests/OverlayTestOverlay/Android.mk b/core/tests/overlaytests/device/OverlayTestOverlay/Android.mk
similarity index 100%
rename from core/tests/overlaytests/OverlayTestOverlay/Android.mk
rename to core/tests/overlaytests/device/OverlayTestOverlay/Android.mk
diff --git a/core/tests/overlaytests/OverlayTestOverlay/AndroidManifest.xml b/core/tests/overlaytests/device/OverlayTestOverlay/AndroidManifest.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTestOverlay/AndroidManifest.xml
rename to core/tests/overlaytests/device/OverlayTestOverlay/AndroidManifest.xml
diff --git a/core/tests/overlaytests/OverlayTestOverlay/res/values/config.xml b/core/tests/overlaytests/device/OverlayTestOverlay/res/values/config.xml
similarity index 100%
rename from core/tests/overlaytests/OverlayTestOverlay/res/values/config.xml
rename to core/tests/overlaytests/device/OverlayTestOverlay/res/values/config.xml
diff --git a/core/tests/overlaytests/host/Android.mk b/core/tests/overlaytests/host/Android.mk
new file mode 100644
index 0000000..d8e1fc1
--- /dev/null
+++ b/core/tests/overlaytests/host/Android.mk
@@ -0,0 +1,32 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE := OverlayHostTests
+LOCAL_JAVA_LIBRARIES := tradefed
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_TARGET_REQUIRED_MODULES := \
+    OverlayHostTests_BadSignatureOverlay \
+    OverlayHostTests_PlatformSignatureStaticOverlay \
+    OverlayHostTests_PlatformSignatureOverlay \
+    OverlayHostTests_PlatformSignatureOverlayV2
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+# Include to build test-apps.
+include $(call all-makefiles-under,$(LOCAL_PATH))
+
diff --git a/core/tests/overlaytests/host/AndroidTest.xml b/core/tests/overlaytests/host/AndroidTest.xml
new file mode 100644
index 0000000..6884623
--- /dev/null
+++ b/core/tests/overlaytests/host/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+
+<configuration description="Host-driven test module config for OverlayHostTests">
+    <option name="test-tag" value="OverlayHostTests" />
+    <option name="test-suite-tag" value="apct" />
+
+    <test class="com.android.tradefed.testtype.HostTest">
+        <option name="class" value="com.android.server.om.hosttest.InstallOverlayTests" />
+    </test>
+</configuration>
diff --git a/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
new file mode 100644
index 0000000..5093710
--- /dev/null
+++ b/core/tests/overlaytests/host/src/com/android/server/om/hosttest/InstallOverlayTests.java
@@ -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.
+ */
+package com.android.server.om.hosttest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class InstallOverlayTests extends BaseHostJUnit4Test {
+
+    private static final String OVERLAY_PACKAGE_NAME =
+            "com.android.server.om.hosttest.signature_overlay";
+
+    @Test
+    public void failToInstallNonPlatformSignedOverlay() throws Exception {
+        try {
+            installPackage("OverlayHostTests_BadSignatureOverlay.apk");
+            fail("installed a non-platform signed overlay");
+        } catch (Exception e) {
+            // Expected.
+        }
+        assertFalse(overlayManagerContainsPackage());
+    }
+
+    @Test
+    public void failToInstallPlatformSignedStaticOverlay() throws Exception {
+        try {
+            installPackage("OverlayHostTests_PlatformSignatureStaticOverlay.apk");
+            fail("installed a static overlay");
+        } catch (Exception e) {
+            // Expected.
+        }
+        assertFalse(overlayManagerContainsPackage());
+    }
+
+    @Test
+    public void succeedToInstallPlatformSignedOverlay() throws Exception {
+        installPackage("OverlayHostTests_PlatformSignatureOverlay.apk");
+        assertTrue(overlayManagerContainsPackage());
+    }
+
+    @Test
+    public void succeedToInstallPlatformSignedOverlayAndUpdate() throws Exception {
+        installPackage("OverlayHostTests_PlatformSignatureOverlay.apk");
+        assertTrue(overlayManagerContainsPackage());
+        assertEquals("v1", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName());
+
+        installPackage("OverlayHostTests_PlatformSignatureOverlayV2.apk");
+        assertTrue(overlayManagerContainsPackage());
+        assertEquals("v2", getDevice().getAppPackageInfo(OVERLAY_PACKAGE_NAME).getVersionName());
+    }
+
+    private boolean overlayManagerContainsPackage() throws Exception {
+        return getDevice().executeShellCommand("cmd overlay list")
+                .contains(OVERLAY_PACKAGE_NAME);
+    }
+}
diff --git a/core/tests/overlaytests/host/test-apps/Android.mk b/core/tests/overlaytests/host/test-apps/Android.mk
new file mode 100644
index 0000000..5c7187e
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/Android.mk
@@ -0,0 +1,16 @@
+# 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.
+
+include $(call all-subdir-makefiles)
+
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
new file mode 100644
index 0000000..b051a82
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/Android.mk
@@ -0,0 +1,52 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+my_package_prefix := com.android.server.om.hosttest.signature_overlay
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_BadSignatureOverlay
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_bad
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureStaticOverlay
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_MANIFEST_FILE := static/AndroidManifest.xml
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_static
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlay
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v1
+LOCAL_AAPT_FLAGS += --version-code 1 --version-name v1
+include $(BUILD_PACKAGE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_PACKAGE_NAME := OverlayHostTests_PlatformSignatureOverlayV2
+LOCAL_COMPATIBILITY_SUITE := general-tests
+LOCAL_CERTIFICATE := platform
+LOCAL_AAPT_FLAGS := --custom-package $(my_package_prefix)_v2
+LOCAL_AAPT_FLAGS += --version-code 2 --version-name v2
+include $(BUILD_PACKAGE)
+
+my_package_prefix :=
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..2d68439
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.server.om.hosttest.signature_overlay">
+    <overlay android:targetPackage="android" />
+</manifest>
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/src/dummy b/core/tests/overlaytests/host/test-apps/SignatureOverlay/src/dummy
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/src/dummy
diff --git a/core/tests/overlaytests/host/test-apps/SignatureOverlay/static/AndroidManifest.xml b/core/tests/overlaytests/host/test-apps/SignatureOverlay/static/AndroidManifest.xml
new file mode 100644
index 0000000..139dd96
--- /dev/null
+++ b/core/tests/overlaytests/host/test-apps/SignatureOverlay/static/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.server.om.hosttest.signature_overlay">
+    <overlay android:targetPackage="android" android:isStatic="true" />
+</manifest>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 0949a90..6c8aaf0 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -350,6 +350,7 @@
         <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
         <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
         <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
+        <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
         <permission name="android.permission.CONTROL_VPN"/>
         <permission name="android.permission.DUMP"/>
         <permission name="android.permission.GET_APP_OPS_STATS"/>
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 6d3ddd5..3034a10 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -166,8 +166,7 @@
 
     @Override
     public void start() {
-        if (isRunning() == false) {
-            nStart(mNativePtr);
+        if (nStart(mNativePtr)) {
             invalidateSelf();
         }
     }
@@ -186,7 +185,7 @@
     private static native int nGetAlpha(long nativePtr);
     private static native void nSetColorFilter(long nativePtr, long nativeFilter);
     private static native boolean nIsRunning(long nativePtr);
-    private static native void nStart(long nativePtr);
+    private static native boolean nStart(long nativePtr);
     private static native void nStop(long nativePtr);
     private static native long nNativeByteSize(long nativePtr);
 }
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index e3740e3..7ad062a 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -163,7 +163,7 @@
     /**
      * Create a drawable by opening a given file path and decoding the bitmap.
      */
-    @SuppressWarnings("unused")
+    @SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" })
     public BitmapDrawable(Resources res, String filepath) {
         this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
         mBitmapState.mTargetDensity = mTargetDensity;
@@ -188,7 +188,7 @@
     /**
      * Create a drawable by decoding a bitmap from the given input stream.
      */
-    @SuppressWarnings("unused")
+    @SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" })
     public BitmapDrawable(Resources res, java.io.InputStream is) {
         this(new BitmapState(BitmapFactory.decodeStream(is)), null);
         mBitmapState.mTargetDensity = mTargetDensity;
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 6c3aea2..f5a6f49 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -42,6 +42,7 @@
 import android.graphics.RectF;
 import android.graphics.Shader;
 import android.graphics.SweepGradient;
+import android.graphics.Xfermode;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -814,6 +815,16 @@
         }
     }
 
+    /**
+     * @param mode to draw this drawable with
+     * @hide
+     */
+    @Override
+    public void setXfermode(@Nullable Xfermode mode) {
+        super.setXfermode(mode);
+        mFillPaint.setXfermode(mode);
+    }
+
     private void buildPathIfDirty() {
         final GradientState st = mGradientState;
         if (mPathIsDirty) {
diff --git a/keystore/java/android/security/KeyStore.java b/keystore/java/android/security/KeyStore.java
index ffa6b76..ded427e 100644
--- a/keystore/java/android/security/KeyStore.java
+++ b/keystore/java/android/security/KeyStore.java
@@ -38,6 +38,7 @@
 import android.security.keystore.KeyExpiredException;
 import android.security.keystore.KeyNotYetValidException;
 import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.StrongBoxUnavailableException;
 import android.security.keystore.UserNotAuthenticatedException;
 import android.util.Log;
 
@@ -65,6 +66,7 @@
     public static final int VALUE_CORRUPTED = 8;
     public static final int UNDEFINED_ACTION = 9;
     public static final int WRONG_PASSWORD = 10;
+    public static final int HARDWARE_TYPE_UNAVAILABLE = -68;
 
     /**
      * Per operation authentication is needed before this operation is valid.
@@ -123,7 +125,6 @@
      */
     public static final int FLAG_STRONGBOX = 1 << 4;
 
-
     // States
     public enum State { UNLOCKED, LOCKED, UNINITIALIZED };
 
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 379e177..f721ed3 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -290,6 +290,9 @@
                 spec.isUserAuthenticationValidWhileOnBody(),
                 spec.isInvalidatedByBiometricEnrollment(),
                 GateKeeper.INVALID_SECURE_USER_ID /* boundToSpecificSecureUserId */);
+        if (spec.isTrustedUserPresenceRequired()) {
+            args.addBoolean(KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED);
+        }
         KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
                 args,
                 mKeymasterAlgorithm,
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index dba3949..d1eb688 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -21,6 +21,7 @@
 import android.security.GateKeeper;
 import android.security.KeyPairGeneratorSpec;
 import android.security.KeyStore;
+import android.security.KeyStoreException;
 import android.security.keymaster.KeyCharacteristics;
 import android.security.keymaster.KeymasterArguments;
 import android.security.keymaster.KeymasterCertificateChain;
@@ -451,7 +452,7 @@
             throw new IllegalStateException("Not initialized");
         }
 
-        final int flags = (mEncryptionAtRestRequired) ? KeyStore.FLAG_ENCRYPTED : 0;
+        int flags = (mEncryptionAtRestRequired) ? KeyStore.FLAG_ENCRYPTED : 0;
         if (((flags & KeyStore.FLAG_ENCRYPTED) != 0)
                 && (mKeyStore.state() != KeyStore.State.UNLOCKED)) {
             throw new IllegalStateException(
@@ -459,6 +460,10 @@
                     + ", but the user has not yet entered the credential");
         }
 
+        if (mSpec.isStrongBoxBacked()) {
+            flags |= KeyStore.FLAG_STRONGBOX;
+        }
+
         byte[] additionalEntropy =
                 KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
                         mRng, (mKeySizeBits + 7) / 8);
@@ -501,8 +506,12 @@
         int errorCode = mKeyStore.generateKey(privateKeyAlias, args, additionalEntropy,
                 mEntryUid, flags, resultingKeyCharacteristics);
         if (errorCode != KeyStore.NO_ERROR) {
-            throw new ProviderException(
-                    "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
+            if (errorCode == KeyStore.HARDWARE_TYPE_UNAVAILABLE) {
+                throw new StrongBoxUnavailableException("Failed to generate key pair");
+            } else {
+                throw new ProviderException(
+                        "Failed to generate key pair", KeyStore.getKeyStoreException(errorCode));
+            }
         }
     }
 
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index fdb885db..9df37f5 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -177,6 +177,9 @@
                 && (keymasterSwEnforcedUserAuthenticators == 0);
         boolean userAuthenticationValidWhileOnBody =
                 keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY);
+        boolean trustedUserPresenceRequred =
+                keyCharacteristics.hwEnforced.getBoolean(
+                    KeymasterDefs.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED);
 
         boolean invalidatedByBiometricEnrollment = false;
         if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_FINGERPRINT
@@ -203,6 +206,7 @@
                 (int) userAuthenticationValidityDurationSeconds,
                 userAuthenticationRequirementEnforcedBySecureHardware,
                 userAuthenticationValidWhileOnBody,
+                trustedUserPresenceRequred,
                 invalidatedByBiometricEnrollment);
     }
 
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 1e2b873..a896c72 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -258,6 +258,7 @@
     private final boolean mRandomizedEncryptionRequired;
     private final boolean mUserAuthenticationRequired;
     private final int mUserAuthenticationValidityDurationSeconds;
+    private final boolean mTrustedUserPresenceRequred;
     private final byte[] mAttestationChallenge;
     private final boolean mUniqueIdIncluded;
     private final boolean mUserAuthenticationValidWhileOnBody;
@@ -287,6 +288,7 @@
             boolean randomizedEncryptionRequired,
             boolean userAuthenticationRequired,
             int userAuthenticationValidityDurationSeconds,
+            boolean trustedUserPresenceRequired,
             byte[] attestationChallenge,
             boolean uniqueIdIncluded,
             boolean userAuthenticationValidWhileOnBody,
@@ -332,6 +334,7 @@
         mBlockModes = ArrayUtils.cloneIfNotEmpty(ArrayUtils.nullToEmpty(blockModes));
         mRandomizedEncryptionRequired = randomizedEncryptionRequired;
         mUserAuthenticationRequired = userAuthenticationRequired;
+        mTrustedUserPresenceRequred = trustedUserPresenceRequired;
         mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
         mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
         mUniqueIdIncluded = uniqueIdIncluded;
@@ -562,6 +565,14 @@
     }
 
     /**
+     * Returns {@code true} if the key is authorized to be used only if a test of user presence has
+     * been performed between the {@code Signature.initSign()} and {@code Signature.sign()} calls.
+     */
+    public boolean isTrustedUserPresenceRequired() {
+        return mTrustedUserPresenceRequred;
+    }
+
+    /**
      * Returns the attestation challenge value that will be placed in attestation certificate for
      * this key pair.
      *
@@ -658,6 +669,7 @@
         private boolean mRandomizedEncryptionRequired = true;
         private boolean mUserAuthenticationRequired;
         private int mUserAuthenticationValidityDurationSeconds = -1;
+        private boolean mTrustedUserPresenceRequired = false;
         private byte[] mAttestationChallenge = null;
         private boolean mUniqueIdIncluded = false;
         private boolean mUserAuthenticationValidWhileOnBody;
@@ -718,6 +730,7 @@
             mUserAuthenticationRequired = sourceSpec.isUserAuthenticationRequired();
             mUserAuthenticationValidityDurationSeconds =
                 sourceSpec.getUserAuthenticationValidityDurationSeconds();
+            mTrustedUserPresenceRequired = sourceSpec.isTrustedUserPresenceRequired();
             mAttestationChallenge = sourceSpec.getAttestationChallenge();
             mUniqueIdIncluded = sourceSpec.isUniqueIdIncluded();
             mUserAuthenticationValidWhileOnBody = sourceSpec.isUserAuthenticationValidWhileOnBody();
@@ -1095,6 +1108,16 @@
         }
 
         /**
+         * Sets whether a test of user presence is required to be performed between the
+         * {@code Signature.initSign()} and {@code Signature.sign()} method calls.
+         */
+        @NonNull
+        public Builder setTrustedUserPresenceRequired(boolean required) {
+            mTrustedUserPresenceRequired = required;
+            return this;
+        }
+
+        /**
          * Sets whether an attestation certificate will be generated for this key pair, and what
          * challenge value will be placed in the certificate.  The attestation certificate chain
          * can be retrieved with with {@link java.security.KeyStore#getCertificateChain(String)}.
@@ -1221,6 +1244,7 @@
                     mRandomizedEncryptionRequired,
                     mUserAuthenticationRequired,
                     mUserAuthenticationValidityDurationSeconds,
+                    mTrustedUserPresenceRequired,
                     mAttestationChallenge,
                     mUniqueIdIncluded,
                     mUserAuthenticationValidWhileOnBody,
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index f553319..864f62a 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -80,6 +80,7 @@
     private final int mUserAuthenticationValidityDurationSeconds;
     private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware;
     private final boolean mUserAuthenticationValidWhileOnBody;
+    private final boolean mTrustedUserPresenceRequired;
     private final boolean mInvalidatedByBiometricEnrollment;
 
     /**
@@ -101,6 +102,7 @@
             int userAuthenticationValidityDurationSeconds,
             boolean userAuthenticationRequirementEnforcedBySecureHardware,
             boolean userAuthenticationValidWhileOnBody,
+            boolean trustedUserPresenceRequired,
             boolean invalidatedByBiometricEnrollment) {
         mKeystoreAlias = keystoreKeyAlias;
         mInsideSecureHardware = insideSecureHardware;
@@ -121,6 +123,7 @@
         mUserAuthenticationRequirementEnforcedBySecureHardware =
                 userAuthenticationRequirementEnforcedBySecureHardware;
         mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
+        mTrustedUserPresenceRequired = trustedUserPresenceRequired;
         mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
     }
 
@@ -301,4 +304,12 @@
     public boolean isInvalidatedByBiometricEnrollment() {
         return mInvalidatedByBiometricEnrollment;
     }
+
+    /**
+     * Returns {@code true} if the key can only be only be used if a test for user presence has
+     * succeeded since Signature.initSign() has been called.
+     */
+    public boolean isTrustedUserPresenceRequired() {
+        return mTrustedUserPresenceRequired;
+    }
 }
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index 7cb8e37..e5fdea7 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -101,6 +101,7 @@
         out.writeBoolean(mSpec.isUniqueIdIncluded());
         out.writeBoolean(mSpec.isUserAuthenticationValidWhileOnBody());
         out.writeBoolean(mSpec.isInvalidatedByBiometricEnrollment());
+        out.writeBoolean(mSpec.isTrustedUserPresenceRequired());
     }
 
     private static Date readDateOrNull(Parcel in) {
@@ -164,6 +165,7 @@
         builder.setUniqueIdIncluded(in.readBoolean());
         builder.setUserAuthenticationValidWhileOnBody(in.readBoolean());
         builder.setInvalidatedByBiometricEnrollment(in.readBoolean());
+        builder.setTrustedUserPresenceRequired(in.readBoolean());
         mSpec = builder.build();
     }
 
diff --git a/keystore/java/android/security/keystore/StrongBoxUnavailableException.java b/keystore/java/android/security/keystore/StrongBoxUnavailableException.java
index ad41a58..66a77ed 100644
--- a/keystore/java/android/security/keystore/StrongBoxUnavailableException.java
+++ b/keystore/java/android/security/keystore/StrongBoxUnavailableException.java
@@ -16,6 +16,9 @@
 
 package android.security.keystore;
 
+import android.security.KeyStore;
+import android.security.KeyStoreException;
+
 import java.security.ProviderException;
 
 /**
@@ -24,5 +27,13 @@
  */
 public class StrongBoxUnavailableException extends ProviderException {
 
+    /**
+     * @hide
+     */
+    public StrongBoxUnavailableException(String message) {
+        super(message,
+                new KeyStoreException(KeyStore.HARDWARE_TYPE_UNAVAILABLE, "No StrongBox available")
+        );
+    }
 }
 
diff --git a/keystore/java/android/security/keystore/UserPresenceUnavailableException.java b/keystore/java/android/security/keystore/UserPresenceUnavailableException.java
new file mode 100644
index 0000000..cf4099e
--- /dev/null
+++ b/keystore/java/android/security/keystore/UserPresenceUnavailableException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.keystore;
+
+import java.security.InvalidAlgorithmParameterException;
+
+/**
+ * Indicates the condition that a proof of user-presence was
+ * requested but this proof was not presented.
+ */
+public class UserPresenceUnavailableException extends InvalidAlgorithmParameterException {
+    /**
+     * Constructs a {@code UserPresenceUnavailableException} without a detail message or cause.
+     */
+    public UserPresenceUnavailableException() {
+        super("No Strong Box available.");
+    }
+
+    /**
+     * Constructs a {@code UserPresenceUnavailableException} using the provided detail message
+     * but no cause.
+     */
+    public UserPresenceUnavailableException(String message) {
+        super(message);
+    }
+
+    /**
+     * Constructs a {@code UserPresenceUnavailableException} using the provided detail message
+     * and cause.
+     */
+    public UserPresenceUnavailableException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
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..a558ff7 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));
   }
@@ -79,7 +105,7 @@
       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 +120,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);
@@ -108,17 +134,20 @@
   std::string 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;
+  for (const auto& package_group : package_groups_) {
+    list = "";
+    for (const auto& package : package_group.packages_) {
+      base::StringAppendF(&list, "%s(%02x), ", package.loaded_package_->GetPackageName().c_str(),
+                          package.loaded_package_->GetPackageId());
+    }
+    LOG(INFO) << base::StringPrintf("PG (%02x): ",
+                                    package_group.dynamic_ref_table.mAssignedPackageId)
+              << list;
   }
 }
 
@@ -157,52 +186,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 +267,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 +286,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 +296,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 +316,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 +454,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 +483,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 +497,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 +516,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 +528,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 +536,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 +604,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 +637,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 +657,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 +697,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 +754,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 +786,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 +809,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 +1015,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/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/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..cc30537 100644
--- a/libs/androidfw/tests/AttributeResolution_test.cpp
+++ b/libs/androidfw/tests/AttributeResolution_test.cpp
@@ -21,6 +21,7 @@
 #include "android-base/file.h"
 #include "android-base/logging.h"
 #include "android-base/macros.h"
+#include "androidfw/AssetManager2.h"
 
 #include "TestHelpers.h"
 #include "data/styles/R.h"
@@ -32,15 +33,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 +48,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) {
@@ -66,14 +65,14 @@
 };
 
 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 +125,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 +170,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/Properties.cpp b/libs/hwui/Properties.cpp
index 4243e7e..6cd283a 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -61,6 +61,8 @@
 bool Properties::skpCaptureEnabled = false;
 bool Properties::enableRTAnimations = true;
 
+bool Properties::runningInEmulator = false;
+
 static int property_get_int(const char* key, int defaultValue) {
     char buf[PROPERTY_VALUE_MAX] = {
             '\0',
@@ -135,6 +137,8 @@
     skpCaptureEnabled = property_get_bool("ro.debuggable", false) &&
                         property_get_bool(PROPERTY_CAPTURE_SKP_ENABLED, false);
 
+    runningInEmulator = property_get_bool(PROPERTY_QEMU_KERNEL, false);
+
     return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw) ||
            (prevDebugStencilClip != debugStencilClip);
 }
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index af4b694..179b97b 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -180,6 +180,11 @@
  */
 #define PROPERTY_CAPTURE_SKP_FILENAME "debug.hwui.skp_filename"
 
+/**
+ * Property for whether this is running in the emulator.
+ */
+#define PROPERTY_QEMU_KERNEL "ro.kernel.qemu"
+
 ///////////////////////////////////////////////////////////////////////////////
 // Misc
 ///////////////////////////////////////////////////////////////////////////////
@@ -261,6 +266,8 @@
     // Used for testing only to change the render pipeline.
     static void overrideRenderPipelineType(RenderPipelineType);
 
+    static bool runningInEmulator;
+
 private:
     static ProfileType sProfileType;
     static bool sDisableProfileBars;
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.cpp b/libs/hwui/hwui/AnimatedImageDrawable.cpp
index 36dd06f..e01bf3d 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.cpp
+++ b/libs/hwui/hwui/AnimatedImageDrawable.cpp
@@ -36,18 +36,27 @@
     mColorFilter = mStagingColorFilter;
 }
 
-void AnimatedImageDrawable::start() {
+bool AnimatedImageDrawable::start() {
     SkAutoExclusive lock(mLock);
+    if (mSkAnimatedImage->isRunning()) {
+        return false;
+    }
 
-    mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
+    if (!mSnapshot) {
+        mSnapshot.reset(mSkAnimatedImage->newPictureSnapshot());
+    }
 
+    // While stopped, update() does not decode, but it does advance the time.
+    // This prevents us from skipping ahead when we resume.
+    const double currentTime = SkTime::GetMSecs();
+    mSkAnimatedImage->update(currentTime);
     mSkAnimatedImage->start();
+    return mSkAnimatedImage->isRunning();
 }
 
 void AnimatedImageDrawable::stop() {
     SkAutoExclusive lock(mLock);
     mSkAnimatedImage->stop();
-    mSnapshot.reset(nullptr);
 }
 
 bool AnimatedImageDrawable::isRunning() {
@@ -120,7 +129,7 @@
     }
 
     SkAutoExclusive lock(mLock);
-    if (mSkAnimatedImage->isRunning()) {
+    if (mSnapshot) {
         canvas->drawPicture(mSnapshot, nullptr, lazyPaint.getMaybeNull());
     } else {
         // TODO: we could potentially keep the cached surface around if there is a paint and we know
diff --git a/libs/hwui/hwui/AnimatedImageDrawable.h b/libs/hwui/hwui/AnimatedImageDrawable.h
index 18764af..1ebb585 100644
--- a/libs/hwui/hwui/AnimatedImageDrawable.h
+++ b/libs/hwui/hwui/AnimatedImageDrawable.h
@@ -58,7 +58,8 @@
 
     double drawStaging(SkCanvas* canvas);
 
-    void start();
+    // Returns true if the animation was started; false otherwise (e.g. it was already running)
+    bool start();
     void stop();
     bool isRunning();
 
diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp
index c7a3014..2fa56f6 100644
--- a/libs/hwui/pipeline/skia/ShaderCache.cpp
+++ b/libs/hwui/pipeline/skia/ShaderCache.cpp
@@ -19,6 +19,7 @@
 #include <log/log.h>
 #include <thread>
 #include "FileBlobCache.h"
+#include "Properties.h"
 #include "utils/TraceUtils.h"
 
 namespace android {
@@ -43,7 +44,11 @@
 void ShaderCache::initShaderDiskCache() {
     ATRACE_NAME("initShaderDiskCache");
     std::lock_guard<std::mutex> lock(mMutex);
-    if (mFilename.length() > 0) {
+
+    // Emulators can switch between different renders either as part of config
+    // or snapshot migration. Also, program binaries may not work well on some
+    // desktop / laptop GPUs. Thus, disable the shader disk cache for emulator builds.
+    if (!Properties::runningInEmulator && mFilename.length() > 0) {
         mBlobCache.reset(new FileBlobCache(maxKeySize, maxValueSize, maxTotalSize, mFilename));
         mInitialized = true;
     }
diff --git a/libs/services/Android.bp b/libs/services/Android.bp
index e5e865f..3d57fbd 100644
--- a/libs/services/Android.bp
+++ b/libs/services/Android.bp
@@ -19,6 +19,7 @@
     srcs: [
         ":IDropBoxManagerService.aidl",
         "src/os/DropBoxManager.cpp",
+        "src/os/StatsDimensionsValue.cpp",
         "src/os/StatsLogEventWrapper.cpp",
     ],
 
diff --git a/libs/services/include/android/os/DropBoxManager.h b/libs/services/include/android/os/DropBoxManager.h
index 2ed203d..75b26c6 100644
--- a/libs/services/include/android/os/DropBoxManager.h
+++ b/libs/services/include/android/os/DropBoxManager.h
@@ -58,6 +58,10 @@
     // are required from the system process.  Returns NULL if the file can't be opened.
     Status addFile(const String16& tag, const string& filename, int flags);
 
+    // Create a new Entry from an already opened file. Takes ownership of the
+    // file descriptor.
+    Status addFile(const String16& tag, int fd, int flags);
+
     class Entry : public virtual RefBase, public Parcelable {
     public:
         Entry();
diff --git a/libs/services/include/android/os/StatsDimensionsValue.h b/libs/services/include/android/os/StatsDimensionsValue.h
new file mode 100644
index 0000000..cc0b056
--- /dev/null
+++ b/libs/services/include/android/os/StatsDimensionsValue.h
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+#ifndef STATS_DIMENSIONS_VALUE_H
+#define STATS_DIMENSIONS_VALUE_H
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <binder/Status.h>
+#include <utils/String16.h>
+#include <vector>
+
+namespace android {
+namespace os {
+
+// Represents a parcelable object. Used to send data from statsd to StatsCompanionService.java.
+class StatsDimensionsValue : public android::Parcelable {
+public:
+    StatsDimensionsValue();
+
+    StatsDimensionsValue(int32_t field, String16 value);
+    StatsDimensionsValue(int32_t field, int32_t value);
+    StatsDimensionsValue(int32_t field, int64_t value);
+    StatsDimensionsValue(int32_t field, bool value);
+    StatsDimensionsValue(int32_t field, float value);
+    StatsDimensionsValue(int32_t field, std::vector<StatsDimensionsValue> value);
+
+    virtual ~StatsDimensionsValue();
+
+    virtual android::status_t writeToParcel(android::Parcel* out) const override;
+    virtual android::status_t readFromParcel(const android::Parcel* in) override;
+
+private:
+    // Keep constants in sync with android/os/StatsDimensionsValue.java
+    // and stats_log.proto's DimensionValue.
+    static const int kStrValueType = 2;
+    static const int kIntValueType = 3;
+    static const int kLongValueType = 4;
+    static const int kBoolValueType = 5;
+    static const int kFloatValueType = 6;
+    static const int kTupleValueType = 7;
+
+    int32_t mField;
+    int32_t mValueType;
+
+    // This isn't very clever, but it isn't used for long-term storage, so it'll do.
+    String16 mStrValue;
+    int32_t mIntValue;
+    int64_t mLongValue;
+    bool mBoolValue;
+    float mFloatValue;
+    std::vector<StatsDimensionsValue> mTupleValue;
+};
+
+}  // namespace os
+}  // namespace android
+
+#endif // STATS_DIMENSIONS_VALUE_H
diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp
index 1c760e8..f5685d9 100644
--- a/libs/services/src/os/DropBoxManager.cpp
+++ b/libs/services/src/os/DropBoxManager.cpp
@@ -202,7 +202,12 @@
         ALOGW("DropboxManager: %s", message.c_str());
         return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, message.c_str());
     }
+    return addFile(tag, fd, flags);
+}
 
+Status
+DropBoxManager::addFile(const String16& tag, int fd, int flags)
+{
     Entry entry(tag, flags, fd);
     return add(entry);
 }
diff --git a/libs/services/src/os/StatsDimensionsValue.cpp b/libs/services/src/os/StatsDimensionsValue.cpp
new file mode 100644
index 0000000..0052e0b
--- /dev/null
+++ b/libs/services/src/os/StatsDimensionsValue.cpp
@@ -0,0 +1,126 @@
+/*
+ * 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 LOG_TAG "StatsDimensionsValue"
+
+#include "android/os/StatsDimensionsValue.h"
+
+#include <cutils/log.h>
+
+using android::Parcel;
+using android::Parcelable;
+using android::status_t;
+using std::vector;
+
+namespace android {
+namespace os {
+
+StatsDimensionsValue::StatsDimensionsValue() {};
+
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, String16 value) :
+    mField(field),
+    mValueType(kStrValueType),
+    mStrValue(value) {
+}
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, int32_t value) :
+    mField(field),
+    mValueType(kIntValueType),
+    mIntValue(value) {
+}
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, int64_t value) :
+    mField(field),
+    mValueType(kLongValueType),
+    mLongValue(value) {
+}
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, bool value) :
+    mField(field),
+    mValueType(kBoolValueType),
+    mBoolValue(value) {
+}
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, float value) :
+    mField(field),
+    mValueType(kFloatValueType),
+    mFloatValue(value) {
+}
+StatsDimensionsValue::StatsDimensionsValue(int32_t field, vector<StatsDimensionsValue> value) :
+    mField(field),
+    mValueType(kTupleValueType),
+    mTupleValue(value) {
+}
+
+StatsDimensionsValue::~StatsDimensionsValue() {}
+
+status_t
+StatsDimensionsValue::writeToParcel(Parcel* out) const {
+    status_t err ;
+
+    err = out->writeInt32(mField);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    err = out->writeInt32(mValueType);
+    if (err != NO_ERROR) {
+        return err;
+    }
+    switch (mValueType) {
+        case kStrValueType:
+            err = out->writeString16(mStrValue);
+            break;
+        case kIntValueType:
+            err = out->writeInt32(mIntValue);
+            break;
+        case kLongValueType:
+            err = out->writeInt64(mLongValue);
+            break;
+        case kBoolValueType:
+            err = out->writeBool(mBoolValue);
+            break;
+        case kFloatValueType:
+            err = out->writeFloat(mFloatValue);
+            break;
+        case kTupleValueType:
+            {
+                int sz = mTupleValue.size();
+                err = out->writeInt32(sz);
+                if (err != NO_ERROR) {
+                    return err;
+                }
+                for (int i = 0; i < sz; ++i) {
+                    err = mTupleValue[i].writeToParcel(out);
+                    if (err != NO_ERROR) {
+                        return err;
+                    }
+                }
+            }
+            break;
+        default:
+            err = UNKNOWN_ERROR;
+            break;
+    }
+    return err;
+}
+
+status_t
+StatsDimensionsValue::readFromParcel(const Parcel* in)
+{
+    // Implement me if desired. We don't currently use this.
+    ALOGE("Cannot do c++ StatsDimensionsValue.readFromParcel(); it is not implemented.");
+    (void)in; // To prevent compile error of unused parameter 'in'
+    return UNKNOWN_ERROR;
+}
+
+}  // namespace os
+}  // namespace android
diff --git a/media/java/android/media/MediaBrowser2.java b/media/java/android/media/MediaBrowser2.java
index be4be3f..5ad4313 100644
--- a/media/java/android/media/MediaBrowser2.java
+++ b/media/java/android/media/MediaBrowser2.java
@@ -16,8 +16,10 @@
 
 package android.media;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.media.update.ApiLoader;
 import android.media.update.MediaBrowser2Provider;
@@ -99,17 +101,17 @@
                 @Nullable Bundle options, @Nullable List<MediaItem2> result) { }
     }
 
-    public MediaBrowser2(Context context, SessionToken2 token, BrowserCallback callback,
-            Executor executor) {
-        super(context, token, callback, executor);
+    public MediaBrowser2(@NonNull Context context, @NonNull SessionToken2 token,
+            @NonNull @CallbackExecutor Executor executor, @NonNull BrowserCallback callback) {
+        super(context, token, executor, callback);
         mProvider = (MediaBrowser2Provider) getProvider();
     }
 
     @Override
     MediaBrowser2Provider createProvider(Context context, SessionToken2 token,
-            ControllerCallback callback, Executor executor) {
+            Executor executor, ControllerCallback callback) {
         return ApiLoader.getProvider(context)
-                .createMediaBrowser2(this, context, token, (BrowserCallback) callback, executor);
+                .createMediaBrowser2(context, this, token, executor, (BrowserCallback) callback);
     }
 
     public void getBrowserRoot(Bundle rootHints) {
diff --git a/media/java/android/media/MediaCodecInfo.java b/media/java/android/media/MediaCodecInfo.java
index f41e33f..44d9099 100644
--- a/media/java/android/media/MediaCodecInfo.java
+++ b/media/java/android/media/MediaCodecInfo.java
@@ -2639,7 +2639,8 @@
         /**
          * Returns the supported range of quality values.
          *
-         * @hide
+         * Quality is implementation-specific. As a general rule, a higher quality
+         * setting results in a better image quality and a lower compression ratio.
          */
         public Range<Integer> getQualityRange() {
             return mQualityRange;
@@ -2751,7 +2752,7 @@
             }
             if (info.containsKey("feature-bitrate-modes")) {
                 for (String mode: info.getString("feature-bitrate-modes").split(",")) {
-                    mBitControl |= parseBitrateMode(mode);
+                    mBitControl |= (1 << parseBitrateMode(mode));
                 }
             }
 
diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java
index d669bc1..6064ec4 100644
--- a/media/java/android/media/MediaController2.java
+++ b/media/java/android/media/MediaController2.java
@@ -16,15 +16,17 @@
 
 package android.media;
 
+import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
-import android.media.MediaSession2.PlaylistParam;
+import android.media.MediaSession2.PlaylistParams;
 import android.media.session.MediaSessionManager;
 import android.media.update.ApiLoader;
 import android.media.update.MediaController2Provider;
@@ -63,8 +65,6 @@
  * @see MediaSessionService2
  * @hide
  */
-// TODO(jaewan): Unhide
-// TODO(jaewan): Revisit comments. Currently MediaBrowser case is missing.
 public class MediaController2 implements AutoCloseable {
     /**
      * Interface for listening to change in activeness of the {@link MediaSession2}.  It's
@@ -130,7 +130,7 @@
          * @param param
          */
         public void onPlaylistChanged(
-                @NonNull List<MediaItem2> list, @NonNull PlaylistParam param) { }
+                @NonNull List<MediaItem2> list, @NonNull PlaylistParams param) { }
 
         /**
          * Called when the playback state is changed.
@@ -239,12 +239,12 @@
      *
      * @param context Context
      * @param token token to connect to
-     * @param callback controller callback to receive changes in
      * @param executor executor to run callbacks on.
+     * @param callback controller callback to receive changes in
      */
     // TODO(jaewan): Put @CallbackExecutor to the constructor.
     public MediaController2(@NonNull Context context, @NonNull SessionToken2 token,
-            @NonNull ControllerCallback callback, @NonNull Executor executor) {
+            @NonNull @CallbackExecutor Executor executor, @NonNull ControllerCallback callback) {
         super();
 
         // This also connects to the token.
@@ -252,14 +252,14 @@
         // session whose session binder is only valid while it's active.
         // prevent a controller from reusable after the
         // session is released and recreated.
-        mProvider = createProvider(context, token, callback, executor);
+        mProvider = createProvider(context, token, executor, callback);
     }
 
     MediaController2Provider createProvider(@NonNull Context context,
-            @NonNull SessionToken2 token, @NonNull ControllerCallback callback,
-            @NonNull Executor executor) {
+            @NonNull SessionToken2 token, @NonNull Executor executor,
+            @NonNull ControllerCallback callback) {
         return ApiLoader.getProvider(context)
-                .createMediaController2(this, context, token, callback, executor);
+                .createMediaController2(context, this, token, executor, callback);
     }
 
     /**
@@ -271,9 +271,7 @@
         mProvider.close_impl();
     }
 
-    /**
-     * @hide
-     */
+    @SystemApi
     public MediaController2Provider getProvider() {
         return mProvider;
     }
@@ -578,7 +576,8 @@
         return mProvider.getPlaylist_impl();
     }
 
-    public @Nullable PlaylistParam getPlaylistParam() {
+    public @Nullable
+    PlaylistParams getPlaylistParam() {
         return mProvider.getPlaylistParam_impl();
     }
 
diff --git a/media/java/android/media/MediaDrm.java b/media/java/android/media/MediaDrm.java
index 063186d..844f2ef 100644
--- a/media/java/android/media/MediaDrm.java
+++ b/media/java/android/media/MediaDrm.java
@@ -1582,6 +1582,7 @@
          * Key to extract the number of successful {@link #openSession} calls
          * from the {@link PersistableBundle} returned by a
          * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
          */
         public static final String OPEN_SESSION_OK_COUNT
             = "/drm/mediadrm/open_session/ok/count";
@@ -1590,8 +1591,341 @@
          * Key to extract the number of failed {@link #openSession} calls
          * from the {@link PersistableBundle} returned by a
          * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
          */
         public static final String OPEN_SESSION_ERROR_COUNT
             = "/drm/mediadrm/open_session/error/count";
+
+        /**
+         * Key to extract the list of error codes that were returned from
+         * {@link #openSession} calls. The key is used to lookup the list
+         * in the {@link PersistableBundle} returned by a {@link #getMetrics}
+         * call.
+         * The list is an array of Long values
+         * ({@link android.os.BaseBundle#getLongArray}).
+         */
+        public static final String OPEN_SESSION_ERROR_LIST
+            = "/drm/mediadrm/open_session/error/list";
+
+        /**
+         * Key to extract the number of successful {@link #closeSession} calls
+         * from the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String CLOSE_SESSION_OK_COUNT
+            = "/drm/mediadrm/close_session/ok/count";
+
+        /**
+         * Key to extract the number of failed {@link #closeSession} calls
+         * from the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String CLOSE_SESSION_ERROR_COUNT
+            = "/drm/mediadrm/close_session/error/count";
+
+        /**
+         * Key to extract the list of error codes that were returned from
+         * {@link #closeSession} calls. The key is used to lookup the list
+         * in the {@link PersistableBundle} returned by a {@link #getMetrics}
+         * call.
+         * The list is an array of Long values
+         * ({@link android.os.BaseBundle#getLongArray}).
+         */
+        public static final String CLOSE_SESSION_ERROR_LIST
+            = "/drm/mediadrm/close_session/error/list";
+
+        /**
+         * Key to extract the start times of sessions. Times are
+         * represented as milliseconds since epoch (1970-01-01T00:00:00Z).
+         * The start times are returned from the {@link PersistableBundle}
+         * from a {@link #getMetrics} call.
+         * The start times are returned as another {@link PersistableBundle}
+         * containing the session ids as keys and the start times as long
+         * values. Use {@link android.os.BaseBundle#keySet} to get the list of
+         * session ids, and then {@link android.os.BaseBundle#getLong} to get
+         * the start time for each session.
+         */
+        public static final String SESSION_START_TIMES_MS
+            = "/drm/mediadrm/session_start_times_ms";
+
+        /**
+         * Key to extract the end times of sessions. Times are
+         * represented as milliseconds since epoch (1970-01-01T00:00:00Z).
+         * The end times are returned from the {@link PersistableBundle}
+         * from a {@link #getMetrics} call.
+         * The end times are returned as another {@link PersistableBundle}
+         * containing the session ids as keys and the end times as long
+         * values. Use {@link android.os.BaseBundle#keySet} to get the list of
+         * session ids, and then {@link android.os.BaseBundle#getLong} to get
+         * the end time for each session.
+         */
+        public static final String SESSION_END_TIMES_MS
+            = "/drm/mediadrm/session_end_times_ms";
+
+        /**
+         * Key to extract the number of successful {@link #getKeyRequest} calls
+         * from the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String GET_KEY_REQUEST_OK_COUNT
+            = "/drm/mediadrm/get_key_request/ok/count";
+
+        /**
+         * Key to extract the number of failed {@link #getKeyRequest}
+         * calls from the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String GET_KEY_REQUEST_ERROR_COUNT
+            = "/drm/mediadrm/get_key_request/error/count";
+
+        /**
+         * Key to extract the list of error codes that were returned from
+         * {@link #getKeyRequest} calls. The key is used to lookup the list
+         * in the {@link PersistableBundle} returned by a {@link #getMetrics}
+         * call.
+         * The list is an array of Long values
+         * ({@link android.os.BaseBundle#getLongArray}).
+         */
+        public static final String GET_KEY_REQUEST_ERROR_LIST
+            = "/drm/mediadrm/get_key_request/error/list";
+
+        /**
+         * Key to extract the average time in microseconds of calls to
+         * {@link #getKeyRequest}. The value is retrieved from the
+         * {@link PersistableBundle} returned from {@link #getMetrics}.
+         * The time is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String GET_KEY_REQUEST_OK_TIME_MICROS
+            = "/drm/mediadrm/get_key_request/ok/average_time_micros";
+
+        /**
+         * Key to extract the number of successful {@link #provideKeyResponse}
+         * calls from the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String PROVIDE_KEY_RESPONSE_OK_COUNT
+            = "/drm/mediadrm/provide_key_response/ok/count";
+
+        /**
+         * Key to extract the number of failed {@link #provideKeyResponse}
+         * calls from the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String PROVIDE_KEY_RESPONSE_ERROR_COUNT
+            = "/drm/mediadrm/provide_key_response/error/count";
+
+        /**
+         * Key to extract the list of error codes that were returned from
+         * {@link #provideKeyResponse} calls. The key is used to lookup the
+         * list in the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The list is an array of Long values
+         * ({@link android.os.BaseBundle#getLongArray}).
+         */
+        public static final String PROVIDE_KEY_RESPONSE_ERROR_LIST
+            = "/drm/mediadrm/provide_key_response/error/list";
+
+        /**
+         * Key to extract the average time in microseconds of calls to
+         * {@link #provideKeyResponse}. The valus is retrieved from the
+         * {@link PersistableBundle} returned from {@link #getMetrics}.
+         * The time is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String PROVIDE_KEY_RESPONSE_OK_TIME_MICROS
+            = "/drm/mediadrm/provide_key_response/ok/average_time_micros";
+
+        /**
+         * Key to extract the number of successful {@link #getProvisionRequest}
+         * calls from the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String GET_PROVISION_REQUEST_OK_COUNT
+            = "/drm/mediadrm/get_provision_request/ok/count";
+
+        /**
+         * Key to extract the number of failed {@link #getProvisionRequest}
+         * calls from the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String GET_PROVISION_REQUEST_ERROR_COUNT
+            = "/drm/mediadrm/get_provision_request/error/count";
+
+        /**
+         * Key to extract the list of error codes that were returned from
+         * {@link #getProvisionRequest} calls. The key is used to lookup the
+         * list in the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The list is an array of Long values
+         * ({@link android.os.BaseBundle#getLongArray}).
+         */
+        public static final String GET_PROVISION_REQUEST_ERROR_LIST
+            = "/drm/mediadrm/get_provision_request/error/list";
+
+        /**
+         * Key to extract the number of successful
+         * {@link #provideProvisionResponse} calls from the
+         * {@link PersistableBundle} returned by a {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String PROVIDE_PROVISION_RESPONSE_OK_COUNT
+            = "/drm/mediadrm/provide_provision_response/ok/count";
+
+        /**
+         * Key to extract the number of failed
+         * {@link #provideProvisionResponse} calls from the
+         * {@link PersistableBundle} returned by a {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String PROVIDE_PROVISION_RESPONSE_ERROR_COUNT
+            = "/drm/mediadrm/provide_provision_response/error/count";
+
+        /**
+         * Key to extract the list of error codes that were returned from
+         * {@link #provideProvisionResponse} calls. The key is used to lookup
+         * the list in the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The list is an array of Long values
+         * ({@link android.os.BaseBundle#getLongArray}).
+         */
+        public static final String PROVIDE_PROVISION_RESPONSE_ERROR_LIST
+            = "/drm/mediadrm/provide_provision_response/error/list";
+
+        /**
+         * Key to extract the number of successful
+         * {@link #getPropertyByteArray} calls were made with the
+         * {@link #PROPERTY_DEVICE_UNIQUE_ID} value. The key is used to lookup
+         * the value in the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String GET_DEVICE_UNIQUE_ID_OK_COUNT
+            = "/drm/mediadrm/get_device_unique_id/ok/count";
+
+        /**
+         * Key to extract the number of failed
+         * {@link #getPropertyByteArray} calls were made with the
+         * {@link #PROPERTY_DEVICE_UNIQUE_ID} value. The key is used to lookup
+         * the value in the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String GET_DEVICE_UNIQUE_ID_ERROR_COUNT
+            = "/drm/mediadrm/get_device_unique_id/error/count";
+
+        /**
+         * Key to extract the list of error codes that were returned from
+         * {@link #getPropertyByteArray} calls with the
+         * {@link #PROPERTY_DEVICE_UNIQUE_ID} value. The key is used to lookup
+         * the list in the {@link PersistableBundle} returned by a
+         * {@link #getMetrics} call.
+         * The list is an array of Long values
+         * ({@link android.os.BaseBundle#getLongArray}).
+         */
+        public static final String GET_DEVICE_UNIQUE_ID_ERROR_LIST
+            = "/drm/mediadrm/get_device_unique_id/error/list";
+
+        /**
+         * Key to extraact the count of {@link KeyStatus#STATUS_EXPIRED} events
+         * that occured. The count is extracted from the
+         * {@link PersistableBundle} returned from a {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String KEY_STATUS_EXPIRED_COUNT
+            = "/drm/mediadrm/key_status/EXPIRED/count";
+
+        /**
+         * Key to extract the count of {@link KeyStatus#STATUS_INTERNAL_ERROR}
+         * events that occured. The count is extracted from the
+         * {@link PersistableBundle} returned from a {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String KEY_STATUS_INTERNAL_ERROR_COUNT
+            = "/drm/mediadrm/key_status/INTERNAL_ERROR/count";
+
+        /**
+         * Key to extract the count of
+         * {@link KeyStatus#STATUS_OUTPUT_NOT_ALLOWED} events that occured.
+         * The count is extracted from the
+         * {@link PersistableBundle} returned from a {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String KEY_STATUS_OUTPUT_NOT_ALLOWED_COUNT
+            = "/drm/mediadrm/key_status_change/OUTPUT_NOT_ALLOWED/count";
+
+        /**
+         * Key to extract the count of {@link KeyStatus#STATUS_PENDING}
+         * events that occured. The count is extracted from the
+         * {@link PersistableBundle} returned from a {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String KEY_STATUS_PENDING_COUNT
+            = "/drm/mediadrm/key_status_change/PENDING/count";
+
+        /**
+         * Key to extract the count of {@link KeyStatus#STATUS_USABLE}
+         * events that occured. The count is extracted from the
+         * {@link PersistableBundle} returned from a {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String KEY_STATUS_USABLE_COUNT
+            = "/drm/mediadrm/key_status_change/USABLE/count";
+
+        /**
+         * Key to extract the count of {@link OnEventListener#onEvent}
+         * calls of type PROVISION_REQUIRED occured. The count is
+         * extracted from the {@link PersistableBundle} returned from a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String EVENT_PROVISION_REQUIRED_COUNT
+            = "/drm/mediadrm/event/PROVISION_REQUIRED/count";
+
+        /**
+         * Key to extract the count of {@link OnEventListener#onEvent}
+         * calls of type KEY_NEEDED occured. The count is
+         * extracted from the {@link PersistableBundle} returned from a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String EVENT_KEY_NEEDED_COUNT
+            = "/drm/mediadrm/event/KEY_NEEDED/count";
+
+        /**
+         * Key to extract the count of {@link OnEventListener#onEvent}
+         * calls of type KEY_EXPIRED occured. The count is
+         * extracted from the {@link PersistableBundle} returned from a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String EVENT_KEY_EXPIRED_COUNT
+            = "/drm/mediadrm/event/KEY_EXPIRED/count";
+
+        /**
+         * Key to extract the count of {@link OnEventListener#onEvent}
+         * calls of type VENDOR_DEFINED. The count is
+         * extracted from the {@link PersistableBundle} returned from a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String EVENT_VENDOR_DEFINED_COUNT
+            = "/drm/mediadrm/event/VENDOR_DEFINED/count";
+
+        /**
+         * Key to extract the count of {@link OnEventListener#onEvent}
+         * calls of type SESSION_RECLAIMED. The count is
+         * extracted from the {@link PersistableBundle} returned from a
+         * {@link #getMetrics} call.
+         * The count is a Long value ({@link android.os.BaseBundle#getLong}).
+         */
+        public static final String EVENT_SESSION_RECLAIMED_COUNT
+            = "/drm/mediadrm/event/SESSION_RECLAIMED/count";
     }
 }
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index c6496eb..e9128e4 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -601,8 +601,6 @@
      * codec specific, but lower values generally result in more efficient
      * (smaller-sized) encoding.
      *
-     * @hide
-     *
      * @see MediaCodecInfo.EncoderCapabilities#getQualityRange()
      */
     public static final String KEY_QUALITY = "quality";
diff --git a/media/java/android/media/MediaItem2.java b/media/java/android/media/MediaItem2.java
index 96a87d5..6a96faa 100644
--- a/media/java/android/media/MediaItem2.java
+++ b/media/java/android/media/MediaItem2.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.text.TextUtils;
 
@@ -32,15 +33,10 @@
  * When it's sent to a controller or browser, it's anonymized and data descriptor wouldn't be sent.
  * <p>
  * This object isn't a thread safe.
- *
  * @hide
  */
-// TODO(jaewan): Unhide and extends from DataSourceDesc.
-//               Note) Feels like an anti-pattern. We should anonymize MediaItem2 to remove *all*
-//                     information in the DataSourceDesc. Why it should extends from this?
-// TODO(jaewan): Move this to updatable
-// Previously MediaBrowser.MediaItem
 public class MediaItem2 {
+    // TODO(jaewan): Keep DataSourceDesc.
     private final int mFlags;
     private MediaMetadata2 mMetadata;
 
diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java
index d7e43ec..5dadcb5 100644
--- a/media/java/android/media/MediaLibraryService2.java
+++ b/media/java/android/media/MediaLibraryService2.java
@@ -28,7 +28,6 @@
 import android.media.update.MediaSession2Provider;
 import android.media.update.MediaSessionService2Provider;
 import android.os.Bundle;
-import android.service.media.MediaBrowserService.BrowserRoot;
 
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -54,7 +53,6 @@
  * declare metadata in the manifest as follows.
  * @hide
  */
-// TODO(jaewan): Unhide
 public abstract class MediaLibraryService2 extends MediaSessionService2 {
     /**
      * This is the interface name that a service implementing a session service should say that it
@@ -69,21 +67,21 @@
         private final MediaLibrarySessionProvider mProvider;
 
         MediaLibrarySession(Context context, MediaPlayerBase player, String id,
-                Executor callbackExecutor, SessionCallback callback, VolumeProvider volumeProvider,
-                int ratingType, PendingIntent sessionActivity) {
-            super(context, player, id, callbackExecutor, callback, volumeProvider, ratingType,
-                    sessionActivity);
+                VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+                Executor callbackExecutor, SessionCallback callback) {
+            super(context, player, id, volumeProvider, ratingType, sessionActivity,
+                    callbackExecutor, callback);
             mProvider = (MediaLibrarySessionProvider) getProvider();
         }
 
         @Override
         MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
-                Executor callbackExecutor, SessionCallback callback, VolumeProvider volumeProvider,
-                int ratingType, PendingIntent sessionActivity) {
+                VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+                Executor callbackExecutor, SessionCallback callback) {
             return ApiLoader.getProvider(context)
-                    .createMediaLibraryService2MediaLibrarySession(this, context, player, id,
-                            callbackExecutor, (MediaLibrarySessionCallback) callback,
-                            volumeProvider, ratingType, sessionActivity);
+                    .createMediaLibraryService2MediaLibrarySession(context, this, player, id,
+                            volumeProvider, ratingType, sessionActivity,
+                            callbackExecutor, (MediaLibrarySessionCallback) callback);
         }
 
         /**
@@ -226,9 +224,9 @@
         }
 
         @Override
-        public MediaLibrarySession build() throws IllegalStateException {
-            return new MediaLibrarySession(mContext, mPlayer, mId, mCallbackExecutor, mCallback,
-                    mVolumeProvider, mRatingType, mSessionActivity);
+        public MediaLibrarySession build() {
+            return new MediaLibrarySession(mContext, mPlayer, mId, mVolumeProvider, mRatingType,
+                    mSessionActivity, mCallbackExecutor, mCallback);
         }
     }
 
@@ -277,7 +275,7 @@
          * @see #EXTRA_OFFLINE
          * @see #EXTRA_SUGGESTED
          */
-        public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+        public static final String EXTRA_RECENT = "android.media.extra.RECENT";
 
         /**
          * The lookup key for a boolean that indicates whether the browser service should return a
@@ -295,7 +293,7 @@
          * @see #EXTRA_RECENT
          * @see #EXTRA_SUGGESTED
          */
-        public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+        public static final String EXTRA_OFFLINE = "android.media.extra.OFFLINE";
 
         /**
          * The lookup key for a boolean that indicates whether the browser service should return a
@@ -303,8 +301,8 @@
          *
          * <p>When creating a media browser for a given media browser service, this key can be
          * supplied as a root hint for retrieving the media items suggested by the media browser
-         * service. The list of media items passed in {@link android.media.browse.MediaBrowser.SubscriptionCallback#onChildrenLoaded(String, List)}
-         * is considered ordered by relevance, first being the top suggestion.
+         * service. The list of media items is considered ordered by relevance, first being the top
+         * suggestion.
          * If the media browser service can provide such media items, the implementation must return
          * the key in the root hint when
          * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
@@ -314,7 +312,7 @@
          * @see #EXTRA_RECENT
          * @see #EXTRA_OFFLINE
          */
-        public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+        public static final String EXTRA_SUGGESTED = "android.media.extra.SUGGESTED";
 
         final private String mRootId;
         final private Bundle mExtras;
diff --git a/media/java/android/media/MediaMetadata2.java b/media/java/android/media/MediaMetadata2.java
index 0e24db6..fcdb4f7 100644
--- a/media/java/android/media/MediaMetadata2.java
+++ b/media/java/android/media/MediaMetadata2.java
@@ -34,6 +34,7 @@
 
 /**
  * Contains metadata about an item, such as the title, artist, etc.
+ *
  * @hide
  */
 // TODO(jaewan): Move this to updatable
@@ -218,7 +219,7 @@
     /**
      * A Uri formatted String representing the content. This value is specific to the
      * service providing the content. It may be used with
-     * {@link MediaController2#playFromUri(Uri, Bundle)}
+     * {@link MediaController2#playFromUri(String, Bundle)}
      * to initiate playback when provided by a {@link MediaBrowser2} connected to
      * the same app.
      */
diff --git a/media/java/android/media/MediaPlayerBase.java b/media/java/android/media/MediaPlayerBase.java
index d638a9f..0efec901 100644
--- a/media/java/android/media/MediaPlayerBase.java
+++ b/media/java/android/media/MediaPlayerBase.java
@@ -16,16 +16,13 @@
 
 package android.media;
 
-import android.media.MediaSession2.PlaylistParam;
-import android.media.session.PlaybackState;
-import android.os.Handler;
+import android.media.MediaSession2.PlaylistParams;
 
 import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
  * Base interfaces for all media players that want media session.
- *
  * @hide
  */
 public abstract class MediaPlayerBase {
@@ -52,7 +49,7 @@
     public abstract PlaybackState2 getPlaybackState();
     public abstract AudioAttributes getAudioAttributes();
 
-    public abstract void setPlaylist(List<MediaItem2> item, PlaylistParam param);
+    public abstract void setPlaylist(List<MediaItem2> item, PlaylistParams param);
     public abstract void setCurrentPlaylistItem(int index);
 
     /**
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index 0e90040..7dffd40 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -20,7 +20,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Activity;
+import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -77,12 +77,6 @@
  * @see MediaSessionService2
  * @hide
  */
-// TODO(jaewan): Unhide
-// TODO(jaewan): Revisit comments. Currently it's borrowed from the MediaSession.
-// TODO(jaewan): Should we support thread safe? It may cause tricky issue such as b/63797089
-// TODO(jaewan): Should we make APIs for MediaSessionService2 public? It's helpful for
-//               developers that doesn't want to override from Browser, but user may not use this
-//               correctly.
 public class MediaSession2 implements AutoCloseable {
     private final MediaSession2Provider mProvider;
 
@@ -349,7 +343,8 @@
         /**
          * Called when a controller set rating on the currently playing contents.
          *
-         * @param
+         * @param controller controller information
+         * @param rating new rating from the controller
          */
         public void onSetRating(@NonNull ControllerInfo controller, @NonNull Rating2 rating) { }
 
@@ -429,7 +424,7 @@
         /**
          * Override to handle requests to play a specific media item represented by a URI.
          */
-        public void prepareFromUri(@NonNull ControllerInfo controller,
+        public void onPrepareFromUri(@NonNull ControllerInfo controller,
                 @NonNull Uri uri, @Nullable Bundle extras) { }
 
         /**
@@ -527,7 +522,7 @@
         /**
          * Set an intent for launching UI for this Session. This can be used as a
          * quick link to an ongoing media screen. The intent should be for an
-         * activity that may be started using {@link Activity#startActivity(Intent)}.
+         * activity that may be started using {@link Context#startActivity(Intent)}.
          *
          * @param pi The intent to launch to show UI for this session.
          */
@@ -555,7 +550,7 @@
         }
 
         /**
-         * Set {@link SessionCallback}.
+         * Set callback for the session.
          *
          * @param executor callback executor
          * @param callback session callback.
@@ -581,7 +576,7 @@
          * @throws IllegalStateException if the session with the same id is already exists for the
          *      package.
          */
-        public abstract MediaSession2 build() throws IllegalStateException;
+        public abstract MediaSession2 build();
     }
 
     /**
@@ -599,12 +594,12 @@
         }
 
         @Override
-        public MediaSession2 build() throws IllegalStateException {
+        public MediaSession2 build() {
             if (mCallback == null) {
                 mCallback = new SessionCallback();
             }
-            return new MediaSession2(mContext, mPlayer, mId, mCallbackExecutor, mCallback,
-                    mVolumeProvider, mRatingType, mSessionActivity);
+            return new MediaSession2(mContext, mPlayer, mId, mVolumeProvider, mRatingType,
+                    mSessionActivity, mCallbackExecutor, mCallback);
         }
     }
 
@@ -624,7 +619,7 @@
                 IMediaSession2Callback callback) {
             mProvider = ApiLoader.getProvider(context)
                     .createMediaSession2ControllerInfoProvider(
-                            this, context, uid, pid, packageName, callback);
+                            context, this, uid, pid, packageName, callback);
         }
 
         /**
@@ -652,11 +647,7 @@
             return mProvider.isTrusted_impl();
         }
 
-        /**
-         * @hide
-         * @return
-         */
-        // TODO(jaewan): SystemApi
+        @SystemApi
         public ControllerInfoProvider getProvider() {
             return mProvider;
         }
@@ -855,7 +846,7 @@
      * Parameter for the playlist.
      */
     // TODO(jaewan): add fromBundle()/toBundle()
-    public static class PlaylistParam {
+    public static class PlaylistParams {
         /**
          * @hide
          */
@@ -915,7 +906,7 @@
 
         private MediaMetadata2 mPlaylistMetadata;
 
-        public PlaylistParam(@RepeatMode int repeatMode, @ShuffleMode int shuffleMode,
+        public PlaylistParams(@RepeatMode int repeatMode, @ShuffleMode int shuffleMode,
                 @Nullable MediaMetadata2 playlistMetadata) {
             mRepeatMode = repeatMode;
             mShuffleMode = shuffleMode;
@@ -949,26 +940,25 @@
      *       framework had to add heuristics to figure out if an app is
      * @hide
      */
-    MediaSession2(Context context, MediaPlayerBase player, String id, Executor callbackExecutor,
-            SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
-            PendingIntent sessionActivity) {
+
+    MediaSession2(Context context, MediaPlayerBase player, String id, VolumeProvider volumeProvider,
+            int ratingType, PendingIntent sessionActivity, Executor callbackExecutor,
+            SessionCallback callback) {
         super();
-        mProvider = createProvider(context, player, id, callbackExecutor, callback,
-                volumeProvider, ratingType, sessionActivity);
+        mProvider = createProvider(context, player, id, volumeProvider, ratingType, sessionActivity,
+                callbackExecutor, callback
+        );
     }
 
     MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
-            Executor callbackExecutor, SessionCallback callback, VolumeProvider volumeProvider,
-            int ratingType, PendingIntent sessionActivity) {
+            VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+            Executor callbackExecutor, SessionCallback callback) {
         return ApiLoader.getProvider(context)
-                .createMediaSession2(this, context, player, id, callbackExecutor,
-                        callback, volumeProvider, ratingType, sessionActivity);
+                .createMediaSession2(context, this, player, id, volumeProvider, ratingType,
+                        sessionActivity, callbackExecutor, callback);
     }
 
-    /**
-     * @hide
-     */
-    // TODO(jaewan): SystemApi
+    @SystemApi
     public MediaSession2Provider getProvider() {
         return mProvider;
     }
@@ -998,10 +988,8 @@
      * @param volumeProvider a volume provider
      * @see #setPlayer(MediaPlayerBase)
      * @see Builder#setVolumeProvider(VolumeProvider)
-     * @throws IllegalArgumentException if a parameter is {@code null}.
      */
-    public void setPlayer(@NonNull MediaPlayerBase player, @NonNull VolumeProvider volumeProvider)
-            throws IllegalArgumentException {
+    public void setPlayer(@NonNull MediaPlayerBase player, @NonNull VolumeProvider volumeProvider) {
         mProvider.setPlayer_impl(player, volumeProvider);
     }
 
@@ -1217,7 +1205,7 @@
         // To match with KEYCODE_MEDIA_SKIP_BACKWARD
     }
 
-    public void setPlaylist(@NonNull List<MediaItem2> playlist, @NonNull PlaylistParam param) {
+    public void setPlaylist(@NonNull List<MediaItem2> playlist, @NonNull PlaylistParams param) {
         mProvider.setPlaylist_impl(playlist, param);
     }
 }
diff --git a/media/java/android/media/MediaSessionService2.java b/media/java/android/media/MediaSessionService2.java
index 19814f0..6b2de06 100644
--- a/media/java/android/media/MediaSessionService2.java
+++ b/media/java/android/media/MediaSessionService2.java
@@ -23,7 +23,6 @@
 import android.app.Service;
 import android.content.Intent;
 import android.media.MediaSession2.ControllerInfo;
-import android.media.session.PlaybackState;
 import android.media.update.ApiLoader;
 import android.media.update.MediaSessionService2Provider;
 import android.os.IBinder;
@@ -89,7 +88,7 @@
  * rejected, the controller will unbind. If it's accepted, the controller will be available to use
  * and keep binding.
  * <p>
- * When playback is started for this session service, {@link #onUpdateNotification(PlaybackState)}
+ * When playback is started for this session service, {@link #onUpdateNotification(PlaybackState2)}
  * is called and service would become a foreground service. It's needed to keep playback after the
  * controller is destroyed. The session service becomes background service when the playback is
  * stopped.
@@ -99,20 +98,8 @@
  * Any app can bind to the session service with controller, but the controller can be used only if
  * the session service accepted the connection request through
  * {@link MediaSession2.SessionCallback#onConnect(ControllerInfo)}.
- *
  * @hide
  */
-// TODO(jaewan): Unhide
-// TODO(jaewan): Can we clean up sessions in onDestroy() automatically instead?
-//               What about currently running SessionCallback when the onDestroy() is called?
-// TODO(jaewan): Protect this with system|privilleged permission - Q.
-// TODO(jaewan): Add permission check for the service to know incoming connection request.
-//               Follow-up questions: What about asking a XML for list of white/black packages for
-//                                    allowing enumeration?
-//                                    We can read the information even when the service is started,
-//                                    so SessionManager.getXXXXService() can only return apps
-//                                    TODO(jaewan): Will be the black/white listing persistent?
-//                                                  In other words, can we cache the rejection?
 public abstract class MediaSessionService2 extends Service {
     private final MediaSessionService2Provider mProvider;
 
@@ -213,7 +200,7 @@
     }
 
     /**
-     * Returned by {@link #onUpdateNotification(PlaybackState)} for making session service
+     * Returned by {@link #onUpdateNotification(PlaybackState2)} for making session service
      * foreground service to keep playback running in the background. It's highly recommended to
      * show media style notification here.
      */
diff --git a/media/java/android/media/PlaybackState2.java b/media/java/android/media/PlaybackState2.java
index 46d6f45..04f211d 100644
--- a/media/java/android/media/PlaybackState2.java
+++ b/media/java/android/media/PlaybackState2.java
@@ -28,7 +28,6 @@
  * the current playback position and extra.
  * @hide
  */
-// TODO(jaewan): Move to updatable
 public final class PlaybackState2 {
     private static final String TAG = "PlaybackState2";
 
diff --git a/media/java/android/media/Rating2.java b/media/java/android/media/Rating2.java
index 67e5e72..93aea6f 100644
--- a/media/java/android/media/Rating2.java
+++ b/media/java/android/media/Rating2.java
@@ -32,7 +32,6 @@
  * through one of the factory methods.
  * @hide
  */
-// TODO(jaewan): Move this to updatable
 public final class Rating2 {
     private static final String TAG = "Rating2";
 
diff --git a/media/java/android/media/SessionToken2.java b/media/java/android/media/SessionToken2.java
index 697a5a8..0abb852 100644
--- a/media/java/android/media/SessionToken2.java
+++ b/media/java/android/media/SessionToken2.java
@@ -37,9 +37,7 @@
  * It can be also obtained by {@link MediaSessionManager}.
  * @hide
  */
-// TODO(jaewan): Unhide. SessionToken2?
 // TODO(jaewan): Move Token to updatable!
-// TODO(jaewan): Find better name for this (SessionToken or Session2Token)
 public final class SessionToken2 {
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE})
@@ -210,7 +208,6 @@
      * Create a {@link Bundle} from this token to share it across processes.
      *
      * @return Bundle
-     * @hide
      */
     public Bundle toBundle() {
         Bundle bundle = new Bundle();
diff --git a/media/java/android/media/update/MediaBrowser2Provider.java b/media/java/android/media/update/MediaBrowser2Provider.java
index e48711d..67680c7 100644
--- a/media/java/android/media/update/MediaBrowser2Provider.java
+++ b/media/java/android/media/update/MediaBrowser2Provider.java
@@ -16,6 +16,7 @@
 
 package android.media.update;
 
+import android.annotation.SystemApi;
 import android.os.Bundle;
 
 /**
diff --git a/media/java/android/media/update/MediaController2Provider.java b/media/java/android/media/update/MediaController2Provider.java
index c5f6b96..8dfb892 100644
--- a/media/java/android/media/update/MediaController2Provider.java
+++ b/media/java/android/media/update/MediaController2Provider.java
@@ -16,11 +16,12 @@
 
 package android.media.update;
 
+import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.media.MediaController2.PlaybackInfo;
 import android.media.MediaItem2;
 import android.media.MediaSession2.Command;
-import android.media.MediaSession2.PlaylistParam;
+import android.media.MediaSession2.PlaylistParams;
 import android.media.PlaybackState2;
 import android.media.Rating2;
 import android.media.SessionToken2;
@@ -59,6 +60,6 @@
     void removePlaylistItem_impl(MediaItem2 index);
     void addPlaylistItem_impl(int index, MediaItem2 item);
 
-    PlaylistParam getPlaylistParam_impl();
+    PlaylistParams getPlaylistParam_impl();
     PlaybackState2 getPlaybackState_impl();
 }
diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java
index dac5784..a568839 100644
--- a/media/java/android/media/update/MediaLibraryService2Provider.java
+++ b/media/java/android/media/update/MediaLibraryService2Provider.java
@@ -16,8 +16,11 @@
 
 package android.media.update;
 
+import android.annotation.SystemApi;
 import android.media.MediaSession2.ControllerInfo;
-import android.os.Bundle; /**
+import android.os.Bundle;
+
+/**
  * @hide
  */
 public interface MediaLibraryService2Provider extends MediaSessionService2Provider {
diff --git a/media/java/android/media/update/MediaSession2Provider.java b/media/java/android/media/update/MediaSession2Provider.java
index 2a68ad1..d32b741 100644
--- a/media/java/android/media/update/MediaSession2Provider.java
+++ b/media/java/android/media/update/MediaSession2Provider.java
@@ -16,14 +16,15 @@
 
 package android.media.update;
 
+import android.annotation.SystemApi;
 import android.media.AudioAttributes;
 import android.media.MediaItem2;
 import android.media.MediaPlayerBase;
-import android.media.MediaSession2;
 import android.media.MediaSession2.Command;
 import android.media.MediaSession2.CommandButton;
 import android.media.MediaSession2.CommandGroup;
 import android.media.MediaSession2.ControllerInfo;
+import android.media.MediaSession2.PlaylistParams;
 import android.media.SessionToken2;
 import android.media.VolumeProvider;
 import android.os.Bundle;
@@ -50,11 +51,8 @@
     void sendCustomCommand_impl(ControllerInfo controller, Command command, Bundle args,
             ResultReceiver receiver);
     void sendCustomCommand_impl(Command command, Bundle args);
-    void setPlaylist_impl(List<MediaItem2> playlist, MediaSession2.PlaylistParam param);
+    void setPlaylist_impl(List<MediaItem2> playlist, PlaylistParams param);
 
-    /**
-     * @hide
-     */
     interface ControllerInfoProvider {
         String getPackageName_impl();
         int getUid_impl();
diff --git a/media/java/android/media/update/MediaSessionService2Provider.java b/media/java/android/media/update/MediaSessionService2Provider.java
index a6b462b..9455da7 100644
--- a/media/java/android/media/update/MediaSessionService2Provider.java
+++ b/media/java/android/media/update/MediaSessionService2Provider.java
@@ -16,6 +16,7 @@
 
 package android.media.update;
 
+import android.annotation.SystemApi;
 import android.content.Intent;
 import android.media.MediaSession2;
 import android.media.MediaSessionService2.MediaNotification;
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 7c222c3..3cd1a99 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -17,9 +17,9 @@
 package android.media.update;
 
 import android.annotation.Nullable;
+import android.annotation.SystemApi;
 import android.app.PendingIntent;
 import android.content.Context;
-import android.media.IMediaSession2Callback;
 import android.media.MediaBrowser2;
 import android.media.MediaBrowser2.BrowserCallback;
 import android.media.MediaController2;
@@ -35,6 +35,7 @@
 import android.media.VolumeProvider;
 import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
 import android.media.update.MediaSession2Provider.ControllerInfoProvider;
+import android.os.IInterface;
 import android.util.AttributeSet;
 import android.widget.MediaControlView2;
 import android.widget.VideoView2;
@@ -46,10 +47,8 @@
  *
  * This interface provides access to constructors and static methods that are otherwise not directly
  * accessible via an implementation object.
- *
  * @hide
  */
-// TODO @SystemApi
 public interface StaticProvider {
     MediaControlView2Provider createMediaControlView2(
             MediaControlView2 instance, ViewProvider superProvider);
@@ -57,25 +56,20 @@
             VideoView2 instance, ViewProvider superProvider,
             @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes);
 
-    MediaSession2Provider createMediaSession2(MediaSession2 mediaSession2, Context context,
-            MediaPlayerBase player, String id, Executor callbackExecutor, SessionCallback callback,
-            VolumeProvider volumeProvider, int ratingType,
-            PendingIntent sessionActivity);
-    ControllerInfoProvider createMediaSession2ControllerInfoProvider(
-            MediaSession2.ControllerInfo instance, Context context, int uid, int pid,
-            String packageName, IMediaSession2Callback callback);
-    MediaController2Provider createMediaController2(
-            MediaController2 instance, Context context, SessionToken2 token,
-            ControllerCallback callback, Executor executor);
-    MediaBrowser2Provider createMediaBrowser2(
-            MediaBrowser2 instance, Context context, SessionToken2 token,
-            BrowserCallback callback, Executor executor);
-    MediaSessionService2Provider createMediaSessionService2(
-            MediaSessionService2 instance);
-    MediaSessionService2Provider createMediaLibraryService2(
-            MediaLibraryService2 instance);
-    MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(
-            MediaLibrarySession instance, Context context, MediaPlayerBase player, String id,
-            Executor callbackExecutor, MediaLibrarySessionCallback callback,
-            VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity);
+    MediaSession2Provider createMediaSession2(Context context, MediaSession2 instance,
+            MediaPlayerBase player, String id, VolumeProvider volumeProvider, int ratingType,
+            PendingIntent sessionActivity, Executor executor, SessionCallback callback);
+    ControllerInfoProvider createMediaSession2ControllerInfoProvider(Context context,
+            MediaSession2.ControllerInfo instance, int uid, int pid,
+            String packageName, IInterface callback);
+    MediaController2Provider createMediaController2(Context context, MediaController2 instance,
+            SessionToken2 token, Executor executor, ControllerCallback callback);
+    MediaBrowser2Provider createMediaBrowser2(Context context, MediaBrowser2 instance,
+            SessionToken2 token, Executor executor, BrowserCallback callback);
+    MediaSessionService2Provider createMediaSessionService2(MediaSessionService2 instance);
+    MediaSessionService2Provider createMediaLibraryService2(MediaLibraryService2 instance);
+    MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(Context context,
+            MediaLibrarySession instance, MediaPlayerBase player, String id,
+            VolumeProvider volumeProvider, int ratingType, PendingIntent sessionActivity,
+            Executor executor, MediaLibrarySessionCallback callback);
 }
diff --git a/media/java/android/media/update/TransportControlProvider.java b/media/java/android/media/update/TransportControlProvider.java
index 5217a9d..0c87063e 100644
--- a/media/java/android/media/update/TransportControlProvider.java
+++ b/media/java/android/media/update/TransportControlProvider.java
@@ -16,14 +16,9 @@
 
 package android.media.update;
 
-import android.media.MediaPlayerBase;
-import android.media.session.PlaybackState;
-import android.os.Handler;
-
 /**
  * @hide
  */
-// TODO(jaewan): SystemApi
 public interface TransportControlProvider {
     void play_impl();
     void pause_impl();
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index cf5882f..d8c975f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -25,6 +25,7 @@
 import android.hardware.camera2.ICameraDeviceUser;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -230,8 +231,8 @@
          * android.hardware.camera2.CaptureResultExtras)
          */
         @Override
-        public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras)
-                throws RemoteException {
+        public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras,
+                PhysicalCaptureResultInfo physicalResults[]) throws RemoteException {
             // TODO Auto-generated method stub
 
         }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 4c96d89..30561ba 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -35,6 +35,7 @@
 import android.hardware.camera2.ICameraDeviceUser;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureResultExtras;
+import android.hardware.camera2.impl.PhysicalCaptureResultInfo;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.utils.SubmitInfo;
 import android.media.Image;
@@ -135,8 +136,8 @@
          * android.hardware.camera2.impl.CameraMetadataNative,
          * android.hardware.camera2.CaptureResultExtras)
          */
-        public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras)
-                throws RemoteException {
+        public void onResultReceived(CameraMetadataNative result, CaptureResultExtras resultExtras,
+                PhysicalCaptureResultInfo physicalResults[]) throws RemoteException {
             // TODO Auto-generated method stub
 
         }
@@ -445,12 +446,14 @@
         // Test both single request and streaming request.
         verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onResultReceived(
                 argThat(matcher),
-                any(CaptureResultExtras.class));
+                any(CaptureResultExtras.class),
+                any(PhysicalCaptureResultInfo[].class));
 
         verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
                 .onResultReceived(
                         argThat(matcher),
-                        any(CaptureResultExtras.class));
+                        any(CaptureResultExtras.class),
+                        any(PhysicalCaptureResultInfo[].class));
     }
 
     @SmallTest
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/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 5dcc927..4156653 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1030,4 +1030,17 @@
     <!-- Content description of zen mode time condition minus button (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_manual_zen_less_time">Less time.</string>
 
+    <!--  Do not disturb: Label for button in enable zen dialog that will turn on zen mode. [CHAR LIMIT=30] -->
+    <string name="zen_mode_enable_dialog_turn_on">Turn on</string>
+    <!-- Button label for generic cancel action [CHAR LIMIT=20] -->
+    <string name="cancel">Cancel</string>
+    <!-- Do not disturb: Title for the Do not Disturb dialog to turn on Do not disturb. [CHAR LIMIT=50]-->
+    <string name="zen_mode_settings_turn_on_dialog_title">Turn on Do Not Disturb</string>
+    <!-- Sound: Summary for the Do not Disturb option when there is no automatic rules turned on. [CHAR LIMIT=NONE]-->
+    <string name="zen_mode_settings_summary_off">Never</string>
+    <!--[CHAR LIMIT=40] Zen Interruption level: Priority.  -->
+    <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>
+
 </resources>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index 9b69304..6b99024 100755
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -219,8 +219,8 @@
             return true;
         }
         BluetoothCodecConfig codecConfig = null;
-        if (mServiceWrapper.getCodecStatus() != null) {
-            codecConfig = mServiceWrapper.getCodecStatus().getCodecConfig();
+        if (mServiceWrapper.getCodecStatus(device) != null) {
+            codecConfig = mServiceWrapper.getCodecStatus(device).getCodecConfig();
         }
         if (codecConfig != null)  {
             return !codecConfig.isMandatoryCodec();
@@ -238,9 +238,9 @@
             return;
         }
         if (enabled) {
-            mService.enableOptionalCodecs();
+            mService.enableOptionalCodecs(device);
         } else {
-            mService.disableOptionalCodecs();
+            mService.disableOptionalCodecs(device);
         }
     }
 
@@ -253,8 +253,8 @@
         // We want to get the highest priority codec, since that's the one that will be used with
         // this device, and see if it is high-quality (ie non-mandatory).
         BluetoothCodecConfig[] selectable = null;
-        if (mServiceWrapper.getCodecStatus() != null) {
-            selectable = mServiceWrapper.getCodecStatus().getCodecsSelectableCapabilities();
+        if (mServiceWrapper.getCodecStatus(device) != null) {
+            selectable = mServiceWrapper.getCodecStatus(device).getCodecsSelectableCapabilities();
             // To get the highest priority, we sort in reverse.
             Arrays.sort(selectable,
                     (a, b) -> {
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
new file mode 100644
index 0000000..7227304
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/EventLogWriter.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.core.instrumentation;
+
+import android.content.Context;
+import android.metrics.LogMaker;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+
+/**
+ * {@link LogWriter} that writes data to eventlog.
+ */
+public class EventLogWriter implements LogWriter {
+
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
+
+    public void visible(Context context, int source, int category) {
+        final LogMaker logMaker = new LogMaker(category)
+                .setType(MetricsProto.MetricsEvent.TYPE_OPEN)
+                .addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source);
+        MetricsLogger.action(logMaker);
+    }
+
+    public void hidden(Context context, int category) {
+        MetricsLogger.hidden(context, category);
+    }
+
+    public void action(int category, int value, Pair<Integer, Object>... taggedData) {
+        if (taggedData == null || taggedData.length == 0) {
+            mMetricsLogger.action(category, value);
+        } else {
+            final LogMaker logMaker = new LogMaker(category)
+                    .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
+                    .setSubtype(value);
+            for (Pair<Integer, Object> pair : taggedData) {
+                logMaker.addTaggedData(pair.first, pair.second);
+            }
+            mMetricsLogger.write(logMaker);
+        }
+    }
+
+    public void action(int category, boolean value, Pair<Integer, Object>... taggedData) {
+        action(category, value ? 1 : 0, taggedData);
+    }
+
+    public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
+        action(context, category, "", taggedData);
+    }
+
+    public void actionWithSource(Context context, int source, int category) {
+        final LogMaker logMaker = new LogMaker(category)
+                .setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+        if (source != MetricsProto.MetricsEvent.VIEW_UNKNOWN) {
+            logMaker.addTaggedData(MetricsProto.MetricsEvent.FIELD_CONTEXT, source);
+        }
+        MetricsLogger.action(logMaker);
+    }
+
+    /** @deprecated use {@link #action(int, int, Pair[])} */
+    @Deprecated
+    public void action(Context context, int category, int value) {
+        MetricsLogger.action(context, category, value);
+    }
+
+    /** @deprecated use {@link #action(int, boolean, Pair[])} */
+    @Deprecated
+    public void action(Context context, int category, boolean value) {
+        MetricsLogger.action(context, category, value);
+    }
+
+    public void action(Context context, int category, String pkg,
+            Pair<Integer, Object>... taggedData) {
+        if (taggedData == null || taggedData.length == 0) {
+            MetricsLogger.action(context, category, pkg);
+        } else {
+            final LogMaker logMaker = new LogMaker(category)
+                    .setType(MetricsProto.MetricsEvent.TYPE_ACTION)
+                    .setPackageName(pkg);
+            for (Pair<Integer, Object> pair : taggedData) {
+                logMaker.addTaggedData(pair.first, pair.second);
+            }
+            MetricsLogger.action(logMaker);
+        }
+    }
+
+    public void count(Context context, String name, int value) {
+        MetricsLogger.count(context, name, value);
+    }
+
+    public void histogram(Context context, String name, int bucket) {
+        MetricsLogger.histogram(context, name, bucket);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/Instrumentable.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/Instrumentable.java
new file mode 100644
index 0000000..dbc61c2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/Instrumentable.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.core.instrumentation;
+
+public interface Instrumentable {
+
+    int METRICS_CATEGORY_UNKNOWN = 0;
+
+    /**
+     * Instrumented name for a view as defined in
+     * {@link com.android.internal.logging.nano.MetricsProto.MetricsEvent}.
+     */
+    int getMetricsCategory();
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
new file mode 100644
index 0000000..4b9f572
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/LogWriter.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.instrumentation;
+
+import android.content.Context;
+import android.util.Pair;
+
+/**
+ * Generic log writer interface.
+ */
+public interface LogWriter {
+
+    /**
+     * Logs a visibility event when view becomes visible.
+     */
+    void visible(Context context, int source, int category);
+
+    /**
+     * Logs a visibility event when view becomes hidden.
+     */
+    void hidden(Context context, int category);
+
+    /**
+     * Logs a user action.
+     */
+    void action(int category, int value, Pair<Integer, Object>... taggedData);
+
+    /**
+     * Logs a user action.
+     */
+    void action(int category, boolean value, Pair<Integer, Object>... taggedData);
+
+    /**
+     * Logs an user action.
+     */
+    void action(Context context, int category, Pair<Integer, Object>... taggedData);
+
+    /**
+     * Logs an user action.
+     */
+    void actionWithSource(Context context, int source, int category);
+
+    /**
+     * Logs an user action.
+     * @deprecated use {@link #action(int, int, Pair[])}
+     */
+    @Deprecated
+    void action(Context context, int category, int value);
+
+    /**
+     * Logs an user action.
+     * @deprecated use {@link #action(int, boolean, Pair[])}
+     */
+    @Deprecated
+    void action(Context context, int category, boolean value);
+
+    /**
+     * Logs an user action.
+     */
+    void action(Context context, int category, String pkg, Pair<Integer, Object>... taggedData);
+
+    /**
+     * Logs a count.
+     */
+    void count(Context context, String name, int value);
+
+    /**
+     * Logs a histogram event.
+     */
+    void histogram(Context context, String name, int bucket);
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
new file mode 100644
index 0000000..1e5b378
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/MetricsFeatureProvider.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.instrumentation;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * FeatureProvider for metrics.
+ */
+public class MetricsFeatureProvider {
+    private List<LogWriter> mLoggerWriters;
+
+    public MetricsFeatureProvider() {
+        mLoggerWriters = new ArrayList<>();
+        installLogWriters();
+    }
+
+    protected void installLogWriters() {
+        mLoggerWriters.add(new EventLogWriter());
+    }
+
+    public void visible(Context context, int source, int category) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.visible(context, source, category);
+        }
+    }
+
+    public void hidden(Context context, int category) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.hidden(context, category);
+        }
+    }
+
+    public void actionWithSource(Context context, int source, int category) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.actionWithSource(context, source, category);
+        }
+    }
+
+    /**
+     * Logs a user action. Includes the elapsed time since the containing
+     * fragment has been visible.
+     */
+    public void action(VisibilityLoggerMixin visibilityLogger, int category, int value) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.action(category, value,
+                    sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible()));
+        }
+    }
+
+    /**
+     * Logs a user action. Includes the elapsed time since the containing
+     * fragment has been visible.
+     */
+    public void action(VisibilityLoggerMixin visibilityLogger, int category, boolean value) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.action(category, value,
+                    sinceVisibleTaggedData(visibilityLogger.elapsedTimeSinceVisible()));
+        }
+    }
+
+    public void action(Context context, int category, Pair<Integer, Object>... taggedData) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.action(context, category, taggedData);
+        }
+    }
+
+    /** @deprecated use {@link #action(VisibilityLoggerMixin, int, int)} */
+    @Deprecated
+    public void action(Context context, int category, int value) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.action(context, category, value);
+        }
+    }
+
+    /** @deprecated use {@link #action(VisibilityLoggerMixin, int, boolean)} */
+    @Deprecated
+    public void action(Context context, int category, boolean value) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.action(context, category, value);
+        }
+    }
+
+    public void action(Context context, int category, String pkg,
+            Pair<Integer, Object>... taggedData) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.action(context, category, pkg, taggedData);
+        }
+    }
+
+    public void count(Context context, String name, int value) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.count(context, name, value);
+        }
+    }
+
+    public void histogram(Context context, String name, int bucket) {
+        for (LogWriter writer : mLoggerWriters) {
+            writer.histogram(context, name, bucket);
+        }
+    }
+
+    public int getMetricsCategory(Object object) {
+        if (object == null || !(object instanceof Instrumentable)) {
+            return MetricsEvent.VIEW_UNKNOWN;
+        }
+        return ((Instrumentable) object).getMetricsCategory();
+    }
+
+    public void logDashboardStartIntent(Context context, Intent intent,
+            int sourceMetricsCategory) {
+        if (intent == null) {
+            return;
+        }
+        final ComponentName cn = intent.getComponent();
+        if (cn == null) {
+            final String action = intent.getAction();
+            if (TextUtils.isEmpty(action)) {
+                // Not loggable
+                return;
+            }
+            action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, action,
+                    Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
+            return;
+        } else if (TextUtils.equals(cn.getPackageName(), context.getPackageName())) {
+            // Going to a Setting internal page, skip click logging in favor of page's own
+            // visibility logging.
+            return;
+        }
+        action(context, MetricsEvent.ACTION_SETTINGS_TILE_CLICK, cn.flattenToString(),
+                Pair.create(MetricsEvent.FIELD_CONTEXT, sourceMetricsCategory));
+    }
+
+    private Pair<Integer, Object> sinceVisibleTaggedData(long timestamp) {
+        return Pair.create(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS, timestamp);
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
new file mode 100644
index 0000000..facce4e
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package com.android.settingslib.core.instrumentation;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.os.AsyncTask;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+
+public class SharedPreferencesLogger implements SharedPreferences {
+
+    private static final String LOG_TAG = "SharedPreferencesLogger";
+
+    private final String mTag;
+    private final Context mContext;
+    private final MetricsFeatureProvider mMetricsFeature;
+    private final Set<String> mPreferenceKeySet;
+
+    public SharedPreferencesLogger(Context context, String tag,
+            MetricsFeatureProvider metricsFeature) {
+        mContext = context;
+        mTag = tag;
+        mMetricsFeature = metricsFeature;
+        mPreferenceKeySet = new ConcurrentSkipListSet<>();
+    }
+
+    @Override
+    public Map<String, ?> getAll() {
+        return null;
+    }
+
+    @Override
+    public String getString(String key, @Nullable String defValue) {
+        return defValue;
+    }
+
+    @Override
+    public Set<String> getStringSet(String key, @Nullable Set<String> defValues) {
+        return defValues;
+    }
+
+    @Override
+    public int getInt(String key, int defValue) {
+        return defValue;
+    }
+
+    @Override
+    public long getLong(String key, long defValue) {
+        return defValue;
+    }
+
+    @Override
+    public float getFloat(String key, float defValue) {
+        return defValue;
+    }
+
+    @Override
+    public boolean getBoolean(String key, boolean defValue) {
+        return defValue;
+    }
+
+    @Override
+    public boolean contains(String key) {
+        return false;
+    }
+
+    @Override
+    public Editor edit() {
+        return new EditorLogger();
+    }
+
+    @Override
+    public void registerOnSharedPreferenceChangeListener(
+            OnSharedPreferenceChangeListener listener) {
+    }
+
+    @Override
+    public void unregisterOnSharedPreferenceChangeListener(
+            OnSharedPreferenceChangeListener listener) {
+    }
+
+    private void logValue(String key, Object value) {
+        logValue(key, value, false /* forceLog */);
+    }
+
+    private void logValue(String key, Object value, boolean forceLog) {
+        final String prefKey = buildPrefKey(mTag, key);
+        if (!forceLog && !mPreferenceKeySet.contains(prefKey)) {
+            // Pref key doesn't exist in set, this is initial display so we skip metrics but
+            // keeps track of this key.
+            mPreferenceKeySet.add(prefKey);
+            return;
+        }
+        // TODO: Remove count logging to save some resource.
+        mMetricsFeature.count(mContext, buildCountName(prefKey, value), 1);
+
+        final Pair<Integer, Object> valueData;
+        if (value instanceof Long) {
+            final Long longVal = (Long) value;
+            final int intVal;
+            if (longVal > Integer.MAX_VALUE) {
+                intVal = Integer.MAX_VALUE;
+            } else if (longVal < Integer.MIN_VALUE) {
+                intVal = Integer.MIN_VALUE;
+            } else {
+                intVal = longVal.intValue();
+            }
+            valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+                    intVal);
+        } else if (value instanceof Integer) {
+            valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+                    value);
+        } else if (value instanceof Boolean) {
+            valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE,
+                    (Boolean) value ? 1 : 0);
+        } else if (value instanceof Float) {
+            valueData = Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE,
+                    value);
+        } else if (value instanceof String) {
+            Log.d(LOG_TAG, "Tried to log string preference " + prefKey + " = " + value);
+            valueData = null;
+        } else {
+            Log.w(LOG_TAG, "Tried to log unloggable object" + value);
+            valueData = null;
+        }
+        if (valueData != null) {
+            // Pref key exists in set, log it's change in metrics.
+            mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE,
+                    Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey),
+                    valueData);
+        }
+    }
+
+    @VisibleForTesting
+    void logPackageName(String key, String value) {
+        final String prefKey = mTag + "/" + key;
+        mMetricsFeature.action(mContext, MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE, value,
+                Pair.create(MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, prefKey));
+    }
+
+    private void safeLogValue(String key, String value) {
+        new AsyncPackageCheck().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, key, value);
+    }
+
+    public static String buildCountName(String prefKey, Object value) {
+        return prefKey + "|" + value;
+    }
+
+    public static String buildPrefKey(String tag, String key) {
+        return tag + "/" + key;
+    }
+
+    private class AsyncPackageCheck extends AsyncTask<String, Void, Void> {
+        @Override
+        protected Void doInBackground(String... params) {
+            String key = params[0];
+            String value = params[1];
+            PackageManager pm = mContext.getPackageManager();
+            try {
+                // Check if this might be a component.
+                ComponentName name = ComponentName.unflattenFromString(value);
+                if (value != null) {
+                    value = name.getPackageName();
+                }
+            } catch (Exception e) {
+            }
+            try {
+                pm.getPackageInfo(value, PackageManager.MATCH_ANY_USER);
+                logPackageName(key, value);
+            } catch (PackageManager.NameNotFoundException e) {
+                // Clearly not a package, and it's unlikely this preference is in prefSet, so
+                // lets force log it.
+                logValue(key, value, true /* forceLog */);
+            }
+            return null;
+        }
+    }
+
+    public class EditorLogger implements Editor {
+        @Override
+        public Editor putString(String key, @Nullable String value) {
+            safeLogValue(key, value);
+            return this;
+        }
+
+        @Override
+        public Editor putStringSet(String key, @Nullable Set<String> values) {
+            safeLogValue(key, TextUtils.join(",", values));
+            return this;
+        }
+
+        @Override
+        public Editor putInt(String key, int value) {
+            logValue(key, value);
+            return this;
+        }
+
+        @Override
+        public Editor putLong(String key, long value) {
+            logValue(key, value);
+            return this;
+        }
+
+        @Override
+        public Editor putFloat(String key, float value) {
+            logValue(key, value);
+            return this;
+        }
+
+        @Override
+        public Editor putBoolean(String key, boolean value) {
+            logValue(key, value);
+            return this;
+        }
+
+        @Override
+        public Editor remove(String key) {
+            return this;
+        }
+
+        @Override
+        public Editor clear() {
+            return this;
+        }
+
+        @Override
+        public boolean commit() {
+            return true;
+        }
+
+        @Override
+        public void apply() {
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
new file mode 100644
index 0000000..7983896
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.core.instrumentation;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+
+import android.os.SystemClock;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
+
+import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;
+
+/**
+ * Logs visibility change of a fragment.
+ */
+public class VisibilityLoggerMixin implements LifecycleObserver, OnResume, OnPause {
+
+    private static final String TAG = "VisibilityLoggerMixin";
+
+    private final int mMetricsCategory;
+
+    private MetricsFeatureProvider mMetricsFeature;
+    private int mSourceMetricsCategory = MetricsProto.MetricsEvent.VIEW_UNKNOWN;
+    private long mVisibleTimestamp;
+
+    /**
+     * The metrics category constant for logging source when a setting fragment is opened.
+     */
+    public static final String EXTRA_SOURCE_METRICS_CATEGORY = ":settings:source_metrics";
+
+    private VisibilityLoggerMixin() {
+        mMetricsCategory = METRICS_CATEGORY_UNKNOWN;
+    }
+
+    public VisibilityLoggerMixin(int metricsCategory, MetricsFeatureProvider metricsFeature) {
+        mMetricsCategory = metricsCategory;
+        mMetricsFeature = metricsFeature;
+    }
+
+    @Override
+    public void onResume() {
+        mVisibleTimestamp = SystemClock.elapsedRealtime();
+        if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
+            mMetricsFeature.visible(null /* context */, mSourceMetricsCategory, mMetricsCategory);
+        }
+    }
+
+    @Override
+    public void onPause() {
+        mVisibleTimestamp = 0;
+        if (mMetricsFeature != null && mMetricsCategory != METRICS_CATEGORY_UNKNOWN) {
+            mMetricsFeature.hidden(null /* context */, mMetricsCategory);
+        }
+    }
+
+    /**
+     * Sets source metrics category for this logger. Source is the caller that opened this UI.
+     */
+    public void setSourceMetricsCategory(Activity activity) {
+        if (mSourceMetricsCategory != MetricsProto.MetricsEvent.VIEW_UNKNOWN || activity == null) {
+            return;
+        }
+        final Intent intent = activity.getIntent();
+        if (intent == null) {
+            return;
+        }
+        mSourceMetricsCategory = intent.getIntExtra(EXTRA_SOURCE_METRICS_CATEGORY,
+                MetricsProto.MetricsEvent.VIEW_UNKNOWN);
+    }
+
+    /** Returns elapsed time since onResume() */
+    public long elapsedTimeSinceVisible() {
+        if (mVisibleTimestamp == 0) {
+            return 0;
+        }
+        return SystemClock.elapsedRealtime() - mVisibleTimestamp;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
new file mode 100644
index 0000000..a20f687
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -0,0 +1,439 @@
+package com.android.settingslib.notification;
+
+/*
+ * 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.
+ */
+
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.net.Uri;
+import android.provider.Settings;
+import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+import android.widget.ScrollView;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.policy.PhoneWindow;
+import com.android.settingslib.R;
+
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Objects;
+
+public class EnableZenModeDialog {
+
+    private static final String TAG = "QSEnableZenModeDialog";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    private static final int[] MINUTE_BUCKETS = ZenModeConfig.MINUTE_BUCKETS;
+    private static final int MIN_BUCKET_MINUTES = MINUTE_BUCKETS[0];
+    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;
+
+    private static final int SECONDS_MS = 1000;
+    private static final int MINUTES_MS = 60 * SECONDS_MS;
+
+    private Uri mForeverId;
+    private int mBucketIndex = -1;
+
+    private AlarmManager mAlarmManager;
+    private int mUserId;
+    private boolean mAttached;
+
+    private Context mContext;
+    private RadioGroup mZenRadioGroup;
+    private LinearLayout mZenRadioGroupContent;
+    private int MAX_MANUAL_DND_OPTIONS = 3;
+
+    public EnableZenModeDialog(Context context) {
+        mContext = context;
+    }
+
+    public Dialog createDialog() {
+        NotificationManager noMan = (NotificationManager) mContext.
+                getSystemService(Context.NOTIFICATION_SERVICE);
+        mForeverId =  Condition.newId(mContext).appendPath("forever").build();
+        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+        mUserId = mContext.getUserId();
+        mAttached = false;
+
+        final AlertDialog.Builder builder = new AlertDialog.Builder(mContext)
+                .setTitle(R.string.zen_mode_settings_turn_on_dialog_title)
+                .setNegativeButton(R.string.cancel, null)
+                .setPositiveButton(R.string.zen_mode_enable_dialog_turn_on,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int which) {
+                                int checkedId = mZenRadioGroup.getCheckedRadioButtonId();
+                                ConditionTag tag = getConditionTagAt(checkedId);
+
+                                if (isForever(tag.condition)) {
+                                    MetricsLogger.action(mContext,
+                                            MetricsProto.MetricsEvent.
+                                                    NOTIFICATION_ZEN_MODE_TOGGLE_ON_FOREVER);
+                                } else if (isAlarm(tag.condition)) {
+                                    MetricsLogger.action(mContext,
+                                            MetricsProto.MetricsEvent.
+                                                    NOTIFICATION_ZEN_MODE_TOGGLE_ON_ALARM);
+                                } else if (isCountdown(tag.condition)) {
+                                    MetricsLogger.action(mContext,
+                                            MetricsProto.MetricsEvent.
+                                                    NOTIFICATION_ZEN_MODE_TOGGLE_ON_COUNTDOWN);
+                                } else {
+                                    Slog.d(TAG, "Invalid manual condition: " + tag.condition);
+                                }
+                                // always triggers priority-only dnd with chosen condition
+                                noMan.setZenMode(Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS,
+                                        getRealConditionId(tag.condition), TAG);
+                            }
+                        });
+
+        View contentView = getContentView();
+        bindConditions(forever());
+        builder.setView(contentView);
+        return builder.create();
+    }
+
+    private void hideAllConditions() {
+        final int N = mZenRadioGroupContent.getChildCount();
+        for (int i = 0; i < N; i++) {
+            mZenRadioGroupContent.getChildAt(i).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);
+        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);
+
+        for (int i = 0; i < MAX_MANUAL_DND_OPTIONS; i++) {
+            final View radioButton = inflater.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,
+                    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) {
+        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() :
+                new ConditionTag();
+        row.setTag(tag);
+        final boolean first = tag.rb == null;
+        if (tag.rb == null) {
+            tag.rb = (RadioButton) mZenRadioGroup.getChildAt(rowId);
+        }
+        tag.condition = condition;
+        final Uri conditionId = getConditionId(tag.condition);
+        if (DEBUG) Log.d(TAG, "bind i=" + mZenRadioGroupContent.indexOfChild(row) + " first="
+                + first + " condition=" + conditionId);
+        tag.rb.setEnabled(enabled);
+        tag.rb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+                if (isChecked) {
+                    tag.rb.setChecked(true);
+                    if (DEBUG) Log.d(TAG, "onCheckedChanged " + conditionId);
+                    MetricsLogger.action(mContext,
+                            MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT);
+                    announceConditionSelection(tag);
+                }
+            }
+        });
+
+        updateUi(tag, row, condition, enabled, rowId, conditionId);
+        row.setVisibility(View.VISIBLE);
+    }
+
+    private ConditionTag getConditionTagAt(int index) {
+        return (ConditionTag) mZenRadioGroupContent.getChildAt(index).getTag();
+    }
+
+    private void bindConditions(Condition c) {
+        // forever
+        bind(forever(), mZenRadioGroupContent.getChildAt(FOREVER_CONDITION_INDEX),
+                FOREVER_CONDITION_INDEX);
+        if (c == null) {
+            bindGenericCountdown();
+            bindNextAlarm(getTimeUntilNextAlarmCondition());
+        } else if (isForever(c)) {
+            getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
+            bindGenericCountdown();
+            bindNextAlarm(getTimeUntilNextAlarmCondition());
+        } else {
+            if (isAlarm(c)) {
+                bindGenericCountdown();
+                bindNextAlarm(c);
+                getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.setChecked(true);
+            } else if (isCountdown(c)) {
+                bindNextAlarm(getTimeUntilNextAlarmCondition());
+                bind(c, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
+                        COUNTDOWN_CONDITION_INDEX);
+                getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
+            } else {
+                Slog.d(TAG, "Invalid manual condition: " + c);
+            }
+        }
+    }
+
+    public static Uri getConditionId(Condition condition) {
+        return condition != null ? condition.id : null;
+    }
+
+    public Condition forever() {
+        Uri foreverId = Condition.newId(mContext).appendPath("forever").build();
+        return new Condition(foreverId, foreverSummary(mContext), "", "", 0 /*icon*/,
+                Condition.STATE_TRUE, 0 /*flags*/);
+    }
+
+    public long getNextAlarm() {
+        final AlarmManager.AlarmClockInfo info = mAlarmManager.getNextAlarmClock(mUserId);
+        return info != null ? info.getTriggerTime() : 0;
+    }
+
+    private boolean isAlarm(Condition c) {
+        return c != null && ZenModeConfig.isValidCountdownToAlarmConditionId(c.id);
+    }
+
+    private boolean isCountdown(Condition c) {
+        return c != null && ZenModeConfig.isValidCountdownConditionId(c.id);
+    }
+
+    private boolean isForever(Condition c) {
+        return c != null && mForeverId.equals(c.id);
+    }
+
+    private Uri getRealConditionId(Condition condition) {
+        return isForever(condition) ? null : getConditionId(condition);
+    }
+
+    private String foreverSummary(Context context) {
+        return context.getString(com.android.internal.R.string.zen_mode_forever);
+    }
+
+    private static void setToMidnight(Calendar calendar) {
+        calendar.set(Calendar.HOUR_OF_DAY, 0);
+        calendar.set(Calendar.MINUTE, 0);
+        calendar.set(Calendar.SECOND, 0);
+        calendar.set(Calendar.MILLISECOND, 0);
+    }
+
+    // Returns a time condition if the next alarm is within the next week.
+    private Condition getTimeUntilNextAlarmCondition() {
+        GregorianCalendar weekRange = new GregorianCalendar();
+        setToMidnight(weekRange);
+        weekRange.add(Calendar.DATE, 6);
+        final long nextAlarmMs = getNextAlarm();
+        if (nextAlarmMs > 0) {
+            GregorianCalendar nextAlarm = new GregorianCalendar();
+            nextAlarm.setTimeInMillis(nextAlarmMs);
+            setToMidnight(nextAlarm);
+
+            if (weekRange.compareTo(nextAlarm) >= 0) {
+                return ZenModeConfig.toNextAlarmCondition(mContext, nextAlarmMs,
+                        ActivityManager.getCurrentUser());
+            }
+        }
+        return null;
+    }
+
+    private void bindGenericCountdown() {
+        mBucketIndex = DEFAULT_BUCKET_INDEX;
+        Condition countdown = ZenModeConfig.toTimeCondition(mContext,
+                MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
+        if (!mAttached || getConditionTagAt(COUNTDOWN_CONDITION_INDEX).condition == null) {
+            bind(countdown, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
+                    COUNTDOWN_CONDITION_INDEX);
+        }
+    }
+
+    private void updateUi(ConditionTag tag, View row, Condition condition,
+            boolean enabled, int rowId, Uri conditionId) {
+        if (tag.lines == null) {
+            tag.lines = row.findViewById(android.R.id.content);
+        }
+        if (tag.line1 == null) {
+            tag.line1 = (TextView) row.findViewById(android.R.id.text1);
+        }
+
+        if (tag.line2 == null) {
+            tag.line2 = (TextView) row.findViewById(android.R.id.text2);
+        }
+
+        final String line1 = !TextUtils.isEmpty(condition.line1) ? condition.line1
+                : condition.summary;
+        final String line2 = condition.line2;
+        tag.line1.setText(line1);
+        if (TextUtils.isEmpty(line2)) {
+            tag.line2.setVisibility(View.GONE);
+        } else {
+            tag.line2.setVisibility(View.VISIBLE);
+            tag.line2.setText(line2);
+        }
+        tag.lines.setEnabled(enabled);
+        tag.lines.setAlpha(enabled ? 1 : .4f);
+
+        tag.lines.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                tag.rb.setChecked(true);
+            }
+        });
+
+        // minus button
+        final ImageView button1 = (ImageView) row.findViewById(android.R.id.button1);
+        button1.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                onClickTimeButton(row, tag, false /*down*/, rowId);
+            }
+        });
+
+        // plus button
+        final ImageView button2 = (ImageView) row.findViewById(android.R.id.button2);
+        button2.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                onClickTimeButton(row, tag, true /*up*/, rowId);
+            }
+        });
+
+        final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
+        if (rowId == COUNTDOWN_CONDITION_INDEX && time > 0) {
+            button1.setVisibility(View.VISIBLE);
+            button2.setVisibility(View.VISIBLE);
+            if (mBucketIndex > -1) {
+                button1.setEnabled(mBucketIndex > 0);
+                button2.setEnabled(mBucketIndex < MINUTE_BUCKETS.length - 1);
+            } else {
+                final long span = time - System.currentTimeMillis();
+                button1.setEnabled(span > MIN_BUCKET_MINUTES * MINUTES_MS);
+                final Condition maxCondition = ZenModeConfig.toTimeCondition(mContext,
+                        MAX_BUCKET_MINUTES, ActivityManager.getCurrentUser());
+                button2.setEnabled(!Objects.equals(condition.summary, maxCondition.summary));
+            }
+
+            button1.setAlpha(button1.isEnabled() ? 1f : .5f);
+            button2.setAlpha(button2.isEnabled() ? 1f : .5f);
+        } else {
+            button1.setVisibility(View.GONE);
+            button2.setVisibility(View.GONE);
+        }
+    }
+
+    private void bindNextAlarm(Condition c) {
+        View alarmContent = mZenRadioGroupContent.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX);
+        ConditionTag tag = (ConditionTag) alarmContent.getTag();
+
+        if (c != null && (!mAttached || tag == null || tag.condition == null)) {
+            bind(c, alarmContent, COUNTDOWN_ALARM_CONDITION_INDEX);
+        }
+
+        // hide the alarm radio button if there isn't a "next alarm condition"
+        tag = (ConditionTag) alarmContent.getTag();
+        boolean showAlarm = tag != null && tag.condition != null;
+        mZenRadioGroup.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility(
+                showAlarm ? View.VISIBLE : View.GONE);
+        alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.GONE);
+    }
+
+    private void onClickTimeButton(View row, ConditionTag tag, boolean up, int rowId) {
+        MetricsLogger.action(mContext, MetricsProto.MetricsEvent.QS_DND_TIME, up);
+        Condition newCondition = null;
+        final int N = MINUTE_BUCKETS.length;
+        if (mBucketIndex == -1) {
+            // not on a known index, search for the next or prev bucket by time
+            final Uri conditionId = getConditionId(tag.condition);
+            final long time = ZenModeConfig.tryParseCountdownConditionId(conditionId);
+            final long now = System.currentTimeMillis();
+            for (int i = 0; i < N; i++) {
+                int j = up ? i : N - 1 - i;
+                final int bucketMinutes = MINUTE_BUCKETS[j];
+                final long bucketTime = now + bucketMinutes * MINUTES_MS;
+                if (up && bucketTime > time || !up && bucketTime < time) {
+                    mBucketIndex = j;
+                    newCondition = ZenModeConfig.toTimeCondition(mContext,
+                            bucketTime, bucketMinutes, ActivityManager.getCurrentUser(),
+                            false /*shortVersion*/);
+                    break;
+                }
+            }
+            if (newCondition == null) {
+                mBucketIndex = DEFAULT_BUCKET_INDEX;
+                newCondition = ZenModeConfig.toTimeCondition(mContext,
+                        MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
+            }
+        } else {
+            // on a known index, simply increment or decrement
+            mBucketIndex = Math.max(0, Math.min(N - 1, mBucketIndex + (up ? 1 : -1)));
+            newCondition = ZenModeConfig.toTimeCondition(mContext,
+                    MINUTE_BUCKETS[mBucketIndex], ActivityManager.getCurrentUser());
+        }
+        bind(newCondition, row, rowId);
+        tag.rb.setChecked(true);
+        announceConditionSelection(tag);
+    }
+
+    private void announceConditionSelection(ConditionTag tag) {
+        // condition will always be priority-only
+        String modeText = mContext.getString(R.string.zen_interruption_level_priority);
+        if (tag.line1 != null) {
+            mZenRadioGroupContent.announceForAccessibility(mContext.getString(
+                    R.string.zen_mode_and_condition, modeText, tag.line1.getText()));
+        }
+    }
+
+    // used as the view tag on condition rows
+    private static class ConditionTag {
+        public RadioButton rb;
+        public View lines;
+        public TextView line1;
+        public TextView line2;
+        public Condition condition;
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java b/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
index 4c52a9f..17e3401 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wrapper/BluetoothA2dpWrapper.java
@@ -43,8 +43,8 @@
     /**
      * Wraps {@code BluetoothA2dp.getCodecStatus}
      */
-    public BluetoothCodecStatus getCodecStatus() {
-        return mService.getCodecStatus();
+    public BluetoothCodecStatus getCodecStatus(BluetoothDevice device) {
+        return mService.getCodecStatus(device);
     }
 
     /**
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
index ece0d51..590bc90 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpProfileTest.java
@@ -123,7 +123,7 @@
         when(mBluetoothA2dp.getConnectionState(any())).thenReturn(
                 BluetoothProfile.STATE_CONNECTED);
         BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
-        when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+        when(mBluetoothA2dpWrapper.getCodecStatus(mDevice)).thenReturn(status);
         BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
         when(status.getCodecConfig()).thenReturn(config);
         when(config.isMandatoryCodec()).thenReturn(false);
@@ -186,7 +186,7 @@
         BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
         BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
         BluetoothCodecConfig[] configs = {config};
-        when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+        when(mBluetoothA2dpWrapper.getCodecStatus(mDevice)).thenReturn(status);
         when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
 
         when(config.isMandatoryCodec()).thenReturn(true);
@@ -201,7 +201,7 @@
         BluetoothCodecStatus status = mock(BluetoothCodecStatus.class);
         BluetoothCodecConfig config = mock(BluetoothCodecConfig.class);
         BluetoothCodecConfig[] configs = {config};
-        when(mBluetoothA2dpWrapper.getCodecStatus()).thenReturn(status);
+        when(mBluetoothA2dpWrapper.getCodecStatus(mDevice)).thenReturn(status);
         when(status.getCodecsSelectableCapabilities()).thenReturn(configs);
 
         when(config.isMandatoryCodec()).thenReturn(false);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
new file mode 100644
index 0000000..8bea51d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/MetricsFeatureProviderTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.instrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Pair;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+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.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class MetricsFeatureProviderTest {
+    private static int CATEGORY = 10;
+    private static boolean SUBTYPE_BOOLEAN = true;
+    private static int SUBTYPE_INTEGER = 1;
+    private static long ELAPSED_TIME = 1000;
+
+    @Mock private LogWriter mockLogWriter;
+    @Mock private VisibilityLoggerMixin mockVisibilityLogger;
+
+    private Context mContext;
+    private MetricsFeatureProvider mProvider;
+
+    @Captor
+    private ArgumentCaptor<Pair> mPairCaptor;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        mProvider = new MetricsFeatureProvider();
+        List<LogWriter> writers = new ArrayList<>();
+        writers.add(mockLogWriter);
+        ReflectionHelpers.setField(mProvider, "mLoggerWriters", writers);
+
+        when(mockVisibilityLogger.elapsedTimeSinceVisible()).thenReturn(ELAPSED_TIME);
+    }
+
+    @Test
+    public void logDashboardStartIntent_intentEmpty_shouldNotLog() {
+        mProvider.logDashboardStartIntent(mContext, null /* intent */,
+                MetricsEvent.SETTINGS_GESTURES);
+
+        verifyNoMoreInteractions(mockLogWriter);
+    }
+
+    @Test
+    public void logDashboardStartIntent_intentHasNoComponent_shouldLog() {
+        final Intent intent = new Intent(Intent.ACTION_ASSIST);
+
+        mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
+
+        verify(mockLogWriter).action(
+                eq(mContext),
+                eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK),
+                anyString(),
+                eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES)));
+    }
+
+    @Test
+    public void logDashboardStartIntent_intentIsExternal_shouldLog() {
+        final Intent intent = new Intent().setComponent(new ComponentName("pkg", "cls"));
+
+        mProvider.logDashboardStartIntent(mContext, intent, MetricsEvent.SETTINGS_GESTURES);
+
+        verify(mockLogWriter).action(
+                eq(mContext),
+                eq(MetricsEvent.ACTION_SETTINGS_TILE_CLICK),
+                anyString(),
+                eq(Pair.create(MetricsEvent.FIELD_CONTEXT, MetricsEvent.SETTINGS_GESTURES)));
+    }
+
+    @Test
+    public void action_BooleanLogsElapsedTime() {
+        mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_BOOLEAN);
+        verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_BOOLEAN), mPairCaptor.capture());
+
+        Pair value = mPairCaptor.getValue();
+        assertThat(value.first instanceof Integer).isTrue();
+        assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS);
+        assertThat(value.second).isEqualTo(ELAPSED_TIME);
+    }
+
+    @Test
+    public void action_IntegerLogsElapsedTime() {
+        mProvider.action(mockVisibilityLogger, CATEGORY, SUBTYPE_INTEGER);
+        verify(mockLogWriter).action(eq(CATEGORY), eq(SUBTYPE_INTEGER), mPairCaptor.capture());
+
+        Pair value = mPairCaptor.getValue();
+        assertThat(value.first instanceof Integer).isTrue();
+        assertThat((int) value.first).isEqualTo(MetricsEvent.NOTIFICATION_SINCE_VISIBLE_MILLIS);
+        assertThat(value.second).isEqualTo(ELAPSED_TIME);
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
new file mode 100644
index 0000000..d558a64
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SharedPreferenceLoggerTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.instrumentation;
+
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_SETTINGS_PREFERENCE_CHANGE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_SETTINGS_PREFERENCE_CHANGE_NAME;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Pair;
+
+import com.android.settingslib.TestConfig;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+
+import com.google.common.truth.Platform;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Answers;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class SharedPreferenceLoggerTest {
+
+    private static final String TEST_TAG = "tag";
+    private static final String TEST_KEY = "key";
+
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+    private Context mContext;
+
+    private ArgumentMatcher<Pair<Integer, Object>> mNamePairMatcher;
+    @Mock
+    private MetricsFeatureProvider mMetricsFeature;
+    private SharedPreferencesLogger mSharedPrefLogger;
+
+    @Before
+    public void init() {
+        MockitoAnnotations.initMocks(this);
+        mSharedPrefLogger = new SharedPreferencesLogger(mContext, TEST_TAG, mMetricsFeature);
+        mNamePairMatcher = pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_NAME, String.class);
+    }
+
+    @Test
+    public void putInt_shouldNotLogInitialPut() {
+        final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+        editor.putInt(TEST_KEY, 1);
+        editor.putInt(TEST_KEY, 1);
+        editor.putInt(TEST_KEY, 1);
+        editor.putInt(TEST_KEY, 2);
+        editor.putInt(TEST_KEY, 2);
+        editor.putInt(TEST_KEY, 2);
+        editor.putInt(TEST_KEY, 2);
+
+        verify(mMetricsFeature, times(6)).action(any(Context.class), anyInt(),
+                argThat(mNamePairMatcher),
+                argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class)));
+    }
+
+    @Test
+    public void putBoolean_shouldNotLogInitialPut() {
+        final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+        editor.putBoolean(TEST_KEY, true);
+        editor.putBoolean(TEST_KEY, true);
+        editor.putBoolean(TEST_KEY, false);
+        editor.putBoolean(TEST_KEY, false);
+        editor.putBoolean(TEST_KEY, false);
+
+
+        verify(mMetricsFeature).action(any(Context.class), anyInt(),
+                argThat(mNamePairMatcher),
+                argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, true)));
+        verify(mMetricsFeature, times(3)).action(any(Context.class), anyInt(),
+                argThat(mNamePairMatcher),
+                argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, false)));
+    }
+
+    @Test
+    public void putLong_shouldNotLogInitialPut() {
+        final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+        editor.putLong(TEST_KEY, 1);
+        editor.putLong(TEST_KEY, 1);
+        editor.putLong(TEST_KEY, 1);
+        editor.putLong(TEST_KEY, 1);
+        editor.putLong(TEST_KEY, 2);
+
+        verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(),
+                argThat(mNamePairMatcher),
+                argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.class)));
+    }
+
+    @Test
+    public void putLong_biggerThanIntMax_shouldLogIntMax() {
+        final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+        final long veryBigNumber = 500L + Integer.MAX_VALUE;
+        editor.putLong(TEST_KEY, 1);
+        editor.putLong(TEST_KEY, veryBigNumber);
+
+        verify(mMetricsFeature).action(any(Context.class), anyInt(),
+                argThat(mNamePairMatcher),
+                argThat(pairMatches(
+                        FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MAX_VALUE)));
+    }
+
+    @Test
+    public void putLong_smallerThanIntMin_shouldLogIntMin() {
+        final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+        final long veryNegativeNumber = -500L + Integer.MIN_VALUE;
+        editor.putLong(TEST_KEY, 1);
+        editor.putLong(TEST_KEY, veryNegativeNumber);
+
+        verify(mMetricsFeature).action(any(Context.class), anyInt(),
+                argThat(mNamePairMatcher),
+                argThat(pairMatches(
+                        FIELD_SETTINGS_PREFERENCE_CHANGE_INT_VALUE, Integer.MIN_VALUE)));
+    }
+
+    @Test
+    public void putFloat_shouldNotLogInitialPut() {
+        final SharedPreferences.Editor editor = mSharedPrefLogger.edit();
+        editor.putFloat(TEST_KEY, 1);
+        editor.putFloat(TEST_KEY, 1);
+        editor.putFloat(TEST_KEY, 1);
+        editor.putFloat(TEST_KEY, 1);
+        editor.putFloat(TEST_KEY, 2);
+
+        verify(mMetricsFeature, times(4)).action(any(Context.class), anyInt(),
+                argThat(mNamePairMatcher),
+                argThat(pairMatches(FIELD_SETTINGS_PREFERENCE_CHANGE_FLOAT_VALUE, Float.class)));
+    }
+
+    @Test
+    public void logPackage_shouldUseLogPackageApi() {
+        mSharedPrefLogger.logPackageName("key", "com.android.settings");
+        verify(mMetricsFeature).action(any(Context.class),
+                eq(ACTION_SETTINGS_PREFERENCE_CHANGE),
+                eq("com.android.settings"),
+                any(Pair.class));
+    }
+
+    private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, Class clazz) {
+        return pair -> pair.first == tag && Platform.isInstanceOfType(pair.second, clazz);
+    }
+
+    private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, boolean bool) {
+        return pair -> pair.first == tag
+                && Platform.isInstanceOfType(pair.second, Integer.class)
+                && pair.second.equals((bool ? 1 : 0));
+    }
+
+    private ArgumentMatcher<Pair<Integer, Object>> pairMatches(int tag, int val) {
+        return pair -> pair.first == tag
+                && Platform.isInstanceOfType(pair.second, Integer.class)
+                && pair.second.equals(val);
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
new file mode 100644
index 0000000..a264886
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixinTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.settingslib.core.instrumentation;
+
+import static com.android.settingslib.core.instrumentation.Instrumentable.METRICS_CATEGORY_UNKNOWN;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.internal.logging.nano.MetricsProto;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class VisibilityLoggerMixinTest {
+
+    @Mock
+    private MetricsFeatureProvider mMetricsFeature;
+
+    private VisibilityLoggerMixin mMixin;
+
+    @Before
+    public void init() {
+        MockitoAnnotations.initMocks(this);
+        mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC, mMetricsFeature);
+    }
+
+    @Test
+    public void shouldLogVisibleOnResume() {
+        mMixin.onResume();
+
+        verify(mMetricsFeature, times(1))
+                .visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.VIEW_UNKNOWN),
+                        eq(TestInstrumentable.TEST_METRIC));
+    }
+
+    @Test
+    public void shouldLogVisibleWithSource() {
+        final Intent sourceIntent = new Intent()
+                .putExtra(VisibilityLoggerMixin.EXTRA_SOURCE_METRICS_CATEGORY,
+                        MetricsProto.MetricsEvent.SETTINGS_GESTURES);
+        final Activity activity = mock(Activity.class);
+        when(activity.getIntent()).thenReturn(sourceIntent);
+        mMixin.setSourceMetricsCategory(activity);
+        mMixin.onResume();
+
+        verify(mMetricsFeature, times(1))
+                .visible(nullable(Context.class), eq(MetricsProto.MetricsEvent.SETTINGS_GESTURES),
+                        eq(TestInstrumentable.TEST_METRIC));
+    }
+
+    @Test
+    public void shouldLogHideOnPause() {
+        mMixin.onPause();
+
+        verify(mMetricsFeature, times(1))
+                .hidden(nullable(Context.class), eq(TestInstrumentable.TEST_METRIC));
+    }
+
+    @Test
+    public void shouldNotLogIfMetricsFeatureIsNull() {
+        mMixin = new VisibilityLoggerMixin(TestInstrumentable.TEST_METRIC, null);
+        mMixin.onResume();
+        mMixin.onPause();
+
+        verify(mMetricsFeature, never())
+                .hidden(nullable(Context.class), anyInt());
+    }
+
+    @Test
+    public void shouldNotLogIfMetricsCategoryIsUnknown() {
+        mMixin = new VisibilityLoggerMixin(METRICS_CATEGORY_UNKNOWN, mMetricsFeature);
+
+        mMixin.onResume();
+        mMixin.onPause();
+
+        verify(mMetricsFeature, never())
+                .hidden(nullable(Context.class), anyInt());
+    }
+
+    private final class TestInstrumentable implements Instrumentable {
+
+        public static final int TEST_METRIC = 12345;
+
+        @Override
+        public int getMetricsCategory() {
+            return TEST_METRIC;
+        }
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index d556db4..6970c86 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.provider.Settings.Global;
 import android.providers.settings.GlobalSettingsProto;
 import android.providers.settings.SecureSettingsProto;
 import android.providers.settings.SettingProto;
@@ -1137,6 +1138,12 @@
         dumpSetting(s, p,
                 Settings.Global.SHOW_MUTE_IN_CRASH_DIALOG,
                 GlobalSettingsProto.SHOW_MUTE_IN_CRASH_DIALOG);
+        dumpSetting(s, p,
+                Settings.Global.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED,
+                GlobalSettingsProto.WIFI_CONNECTED_MAC_RANDOMIZATION_ENABLED);
+        dumpSetting(s, p,
+                Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
+                GlobalSettingsProto.CHAINED_BATTERY_ATTRIBUTION_ENABLED);
     }
 
     /** Dump a single {@link SettingsState.Setting} to a proto buf */
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 42b7213..1fc36be 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -200,6 +200,9 @@
     <!-- to access instant apps -->
     <uses-permission android:name="android.permission.ACCESS_INSTANT_APPS" />
 
+    <!-- to control remote app transitions -->
+    <uses-permission android:name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS" />
+
     <!-- to change themes - light or dark -->
     <uses-permission android:name="android.permission.CHANGE_OVERLAY_PACKAGES" />
 
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
index 903ff72..32eb5d5 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/VolumeDialogController.java
@@ -172,5 +172,6 @@
         void onScreenOff();
         void onShowSafetyWarning(int flags);
         void onAccessibilityModeChanged(Boolean showA11yStream);
+        void onConnectedDeviceChanged(String deviceName);
     }
 }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index a648345e..30d1352 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -14,6 +14,7 @@
 
 package com.android.systemui.plugins.qs;
 
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
@@ -65,6 +66,18 @@
     default void setHasNotifications(boolean hasNotifications) {
     }
 
+    /**
+     * We need this to handle nested scrolling for QS..
+     * Normally we would do this with requestDisallowInterceptTouchEvent, but when both the
+     * scroll containers are using the same touch slop, they try to start scrolling at the
+     * same time and NotificationPanelView wins, this lets QS win.
+     *
+     * TODO: Do this using NestedScroll capabilities.
+     */
+    default boolean onInterceptTouchEvent(MotionEvent event) {
+        return isCustomizing();
+    }
+
     @ProvidesInterface(version = HeightListener.VERSION)
     interface HeightListener {
         int VERSION = 1;
diff --git a/packages/SystemUI/res/drawable/ic_swap.xml b/packages/SystemUI/res/drawable/ic_swap.xml
new file mode 100644
index 0000000..30da2a9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_swap.xml
@@ -0,0 +1,25 @@
+<!--
+    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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorForeground">
+    <path
+        android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"
+        android:fillColor="#FFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/fingerprint_dialog.xml b/packages/SystemUI/res/layout/fingerprint_dialog.xml
index e5f62b3..161f13f 100644
--- a/packages/SystemUI/res/layout/fingerprint_dialog.xml
+++ b/packages/SystemUI/res/layout/fingerprint_dialog.xml
@@ -99,6 +99,7 @@
             android:layout_height="@dimen/fingerprint_dialog_fp_icon_size"
             android:layout_gravity="center_horizontal"
             android:scaleType="centerInside"
+            android:contentDescription="@string/accessibility_fingerprint_dialog_fingerprint_icon"
             android:src="@drawable/fingerprint_icon"/>
 
         <TextView
@@ -112,6 +113,8 @@
             android:textSize="12sp"
             android:visibility="invisible"
             android:gravity="center_horizontal"
+            android:accessibilityLiveRegion="polite"
+            android:contentDescription="@string/accessibility_fingerprint_dialog_help_area"
             android:textColor="@color/fingerprint_error_message_color"/>
 
         <LinearLayout android:id="@+id/buttonPanel"
diff --git a/packages/SystemUI/res/layout/volume_dialog_row.xml b/packages/SystemUI/res/layout/volume_dialog_row.xml
index 3590b76..1d1f95b 100644
--- a/packages/SystemUI/res/layout/volume_dialog_row.xml
+++ b/packages/SystemUI/res/layout/volume_dialog_row.xml
@@ -38,23 +38,34 @@
             android:maxLines="1"
             android:textColor="?android:attr/colorControlNormal"
             android:textAppearance="?android:attr/textAppearanceSmall" />
-        <TextView
-            android:id="@+id/volume_row_connected_device"
-            android:visibility="gone"
+        <LinearLayout
+            android:id="@+id/output_chooser"
+            android:orientation="vertical"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:ellipsize="end"
-            android:maxLines="1"
-            android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" />
-        <com.android.keyguard.AlphaOptimizedImageButton
-            android:id="@+id/output_chooser"
-            style="@style/VolumeButtons"
+            android:minWidth="48dp"
+            android:minHeight="48dp"
+            android:paddingTop="10dp"
             android:background="?android:selectableItemBackgroundBorderless"
-            android:layout_width="@dimen/volume_button_size"
-            android:layout_height="wrap_content"
-            android:layout_centerVertical="true"
-            android:src="@drawable/ic_volume_expand_animation"
-            android:soundEffectsEnabled="false" />
+            android:gravity="center">
+            <TextView
+                android:id="@+id/volume_row_connected_device"
+                android:visibility="gone"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:ellipsize="end"
+                android:maxLines="1"
+                android:textAppearance="@style/TextAppearance.QS.DetailItemSecondary" />
+            <com.android.keyguard.AlphaOptimizedImageButton
+                android:id="@+id/output_chooser_button"
+                android:layout_width="24dp"
+                android:layout_height="24dp"
+                android:background="?android:selectableItemBackgroundBorderless"
+                style="@style/VolumeButtons"
+                android:layout_centerVertical="true"
+                android:src="@drawable/ic_swap"
+                android:soundEffectsEnabled="false" />
+        </LinearLayout>
     </LinearLayout>
     <FrameLayout
         android:id="@+id/volume_row_slider_frame"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7ee9eda..d58c725 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -336,6 +336,8 @@
     <dimen name="qs_footer_padding_end">24dp</dimen>
     <dimen name="qs_footer_icon_size">16dp</dimen>
 
+    <dimen name="qs_notif_collapsed_space">64dp</dimen>
+
     <!-- Desired qs icon overlay size. -->
     <dimen name="qs_detail_icon_overlay_size">24dp</dimen>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8ea1225..6d61a0c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -259,6 +259,13 @@
     <!-- Button name for "Cancel". [CHAR LIMIT=NONE] -->
     <string name="cancel">Cancel</string>
 
+    <!-- Content description of the fingerprint icon when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_fingerprint_dialog_fingerprint_icon">Fingerprint icon</string>
+    <!-- Content description of the application icon when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_fingerprint_dialog_app_icon">Application icon</string>
+    <!-- Content description for the error/help message are when the system-provided fingerprint dialog is showing, for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_fingerprint_dialog_help_area">Help message area</string>
+
     <!-- Content description of the compatibility zoom button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_compatibility_zoom_button">Compatibility zoom button.</string>
 
@@ -1499,6 +1506,10 @@
     <string name="notification_channel_disabled">You won\'t see these notifications anymore</string>
 
     <!-- Notification Inline controls: continue receiving notifications prompt, channel level -->
+    <string name="inline_blocking_helper">You usually dismiss these notifications.
+    \nKeep showing them?</string>
+
+    <!-- Notification Inline controls: continue receiving notifications prompt, channel level -->
     <string name="inline_keep_showing">Keep showing these notifications?</string>
 
     <!-- Notification inline controls: block notifications button -->
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 3538327..2c0e95b 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -61,7 +61,7 @@
  */
 public class SystemUIApplication extends Application implements SysUiServiceProvider {
 
-    private static final String TAG = "SystemUIService";
+    public static final String TAG = "SystemUIService";
     private static final boolean DEBUG = false;
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 11e0f28..ddf0bd0 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -20,11 +20,15 @@
 import android.content.Intent;
 import android.os.Build;
 import android.os.IBinder;
+import android.os.Process;
 import android.os.SystemProperties;
+import android.util.Slog;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
+import com.android.internal.os.BinderInternal;
+
 public class SystemUIService extends Service {
 
     @Override
@@ -36,6 +40,21 @@
         if (Build.IS_DEBUGGABLE && SystemProperties.getBoolean("debug.crash_sysui", false)) {
             throw new RuntimeException();
         }
+
+        if (Build.IS_DEBUGGABLE) {
+            // b/71353150 - looking for leaked binder proxies
+            BinderInternal.nSetBinderProxyCountEnabled(true);
+            BinderInternal.nSetBinderProxyCountWatermarks(1000,900);
+            BinderInternal.setBinderProxyCountCallback(
+                    new BinderInternal.BinderProxyLimitListener() {
+                        @Override
+                        public void onLimitReached(int uid) {
+                            Slog.w(SystemUIApplication.TAG,
+                                    "uid " + uid + " sent too many Binder proxies to uid "
+                                    + Process.myUid());
+                        }
+                    }, Dependency.get(Dependency.MAIN_HANDLER));
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java
index 19bc2ec..9779937 100644
--- a/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/fingerprint/FingerprintDialogView.java
@@ -146,7 +146,7 @@
         subtitle.setText(mBundle.getCharSequence(FingerprintDialog.KEY_SUBTITLE));
         description.setText(mBundle.getCharSequence(FingerprintDialog.KEY_DESCRIPTION));
         negative.setText(mBundle.getCharSequence(FingerprintDialog.KEY_NEGATIVE_TEXT));
-        image.setImageDrawable(getAppIcon());
+        setAppIcon(image);
 
         final CharSequence positiveText =
                 mBundle.getCharSequence(FingerprintDialog.KEY_POSITIVE_TEXT);
@@ -190,6 +190,7 @@
     private void showMessage(String message) {
         mHandler.removeMessages(FingerprintDialogImpl.MSG_CLEAR_MESSAGE);
         mErrorText.setText(message);
+        mErrorText.setContentDescription(message);
         mErrorText.setVisibility(View.VISIBLE);
         mHandler.sendMessageDelayed(mHandler.obtainMessage(FingerprintDialogImpl.MSG_CLEAR_MESSAGE),
                 FingerprintDialog.HIDE_DIALOG_DELAY);
@@ -205,12 +206,16 @@
                 false /* userCanceled */), FingerprintDialog.HIDE_DIALOG_DELAY);
     }
 
-    private Drawable getAppIcon() {
+    private void setAppIcon(ImageView image) {
         final ActivityManager.RunningTaskInfo taskInfo = mActivityManagerWrapper.getRunningTask();
         final ComponentName cn = taskInfo.topActivity;
         final int userId = mActivityManagerWrapper.getCurrentUserId();
         final ActivityInfo activityInfo = mPackageManageWrapper.getActivityInfo(cn, userId);
-        return mActivityManagerWrapper.getBadgedActivityIcon(activityInfo, userId);
+        image.setImageDrawable(mActivityManagerWrapper.getBadgedActivityIcon(activityInfo, userId));
+        image.setContentDescription(
+                getResources().getString(R.string.accessibility_fingerprint_dialog_app_icon)
+                        + " "
+                        + mActivityManagerWrapper.getBadgedActivityLabel(activityInfo, userId));
     }
 
     public WindowManager.LayoutParams getLayoutParams() {
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
index 8f41a60..bd130f4 100644
--- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimates.java
@@ -2,7 +2,25 @@
 
 public interface EnhancedEstimates {
 
+    /**
+     * Returns a boolean indicating if the hybrid notification should be used.
+     */
     boolean isHybridNotificationEnabled();
 
+    /**
+     * Returns an estimate object if the feature is enabled.
+     */
     Estimate getEstimate();
+
+    /**
+     * Returns a long indicating the amount of time remaining in milliseconds under which we will
+     * show a regular warning to the user.
+     */
+    long getLowWarningThreshold();
+
+    /**
+     * Returns a long indicating the amount of time remaining in milliseconds under which we will
+     * show a severe warning to the user.
+     */
+    long getSevereWarningThreshold();
 }
diff --git a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
index d447542..5686d80 100644
--- a/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/power/EnhancedEstimatesImpl.java
@@ -13,4 +13,14 @@
     public Estimate getEstimate() {
         return null;
     }
+
+    @Override
+    public long getLowWarningThreshold() {
+        return 0;
+    }
+
+    @Override
+    public long getSevereWarningThreshold() {
+        return 0;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 736286f..aa56694 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -99,6 +99,8 @@
     private long mWarningTriggerTimeMs;
 
     private Estimate mEstimate;
+    private long mLowWarningThreshold;
+    private long mSevereWarningThreshold;
     private boolean mWarning;
     private boolean mPlaySound;
     private boolean mInvalidCharger;
@@ -142,11 +144,18 @@
     @Override
     public void updateEstimate(Estimate estimate) {
         mEstimate = estimate;
-        if (estimate.estimateMillis <= PowerUI.THREE_HOURS_IN_MILLIS) {
+        if (estimate.estimateMillis <= mLowWarningThreshold) {
             mWarningTriggerTimeMs = System.currentTimeMillis();
         }
     }
 
+    @Override
+    public void updateThresholds(long lowThreshold, long severeThreshold) {
+        mLowWarningThreshold = lowThreshold;
+        mSevereWarningThreshold = severeThreshold;
+    }
+
+
     private void updateNotification() {
         if (DEBUG) Slog.d(TAG, "updateNotification mWarning=" + mWarning + " mPlaySound="
                 + mPlaySound + " mInvalidCharger=" + mInvalidCharger);
@@ -181,7 +190,8 @@
     }
 
     protected void showWarningNotification() {
-        final String percentage = NumberFormat.getPercentInstance().format((double) mBatteryLevel / 100.0);
+        final String percentage = NumberFormat.getPercentInstance()
+                .format((double) mBatteryLevel / 100.0);
 
         // get standard notification copy
         String title = mContext.getString(R.string.battery_low_title);
@@ -214,7 +224,8 @@
         }
         // Make the notification red if the percentage goes below a certain amount or the time
         // remaining estimate is disabled
-        if (mEstimate == null || mBucket < 0) {
+        if (mEstimate == null || mBucket < 0
+                || mEstimate.estimateMillis < mSevereWarningThreshold) {
             nb.setColor(Utils.getColorAttr(mContext, android.R.attr.colorError));
         }
         nb.addAction(0,
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index c5aab60..b43e99b 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -269,6 +269,8 @@
             if (estimate != null) {
                 mTimeRemaining = estimate.estimateMillis;
                 mWarnings.updateEstimate(estimate);
+                mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(),
+                        mEnhancedEstimates.getSevereWarningThreshold());
             }
         }
 
@@ -292,7 +294,7 @@
                 && !isPowerSaver
                 && (((bucket < oldBucket || oldPlugged) && bucket < 0)
                         || (mEnhancedEstimates.isHybridNotificationEnabled()
-                                && timeRemaining < THREE_HOURS_IN_MILLIS
+                                && timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
                                 && isHourLess(oldTimeRemaining, timeRemaining)))
                 && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
     }
@@ -306,7 +308,7 @@
     boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
             long timeRemaining, boolean isPowerSaver) {
         final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled()
-                && timeRemaining > THREE_HOURS_IN_MILLIS;
+                && timeRemaining > mEnhancedEstimates.getLowWarningThreshold();
         final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0);
         return isPowerSaver
                 || plugged
@@ -485,6 +487,7 @@
     public interface WarningsUI {
         void update(int batteryLevel, int bucket, long screenOffTime);
         void updateEstimate(Estimate estimate);
+        void updateThresholds(long lowThreshold, long severeThreshold);
         void dismissLowBatteryWarning();
         void showLowBatteryWarning(boolean playSound);
         void dismissInvalidChargerWarning();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
index 8f18800..95185c0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java
@@ -244,10 +244,8 @@
             mFirstPageDelayedAnimator = new TouchAnimator.Builder()
                     .setStartDelay(EXPANDED_TILE_DELAY)
                     .addFloat(tileLayout, "alpha", 0, 1)
-                    .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
                     .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
                     .addFloat(mQsPanel.getFooter().getView(), "alpha", 0, 1).build();
-            mAllViews.add(mQsPanel.getPageIndicator());
             mAllViews.add(mQsPanel.getDivider());
             mAllViews.add(mQsPanel.getFooter().getView());
             float px = 0;
@@ -265,7 +263,6 @@
         }
         mNonfirstPageAnimator = new TouchAnimator.Builder()
                 .addFloat(mQuickQsPanel, "alpha", 1, 0)
-                .addFloat(mQsPanel.getPageIndicator(), "alpha", 0, 1)
                 .addFloat(mQsPanel.getDivider(), "alpha", 0, 1)
                 .setListener(mNonFirstPageListener)
                 .setEndDelay(.5f)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 7320b86..6b0d592 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -17,11 +17,11 @@
 package com.android.systemui.qs;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Path;
 import android.graphics.Point;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.View;
 import android.widget.FrameLayout;
 
@@ -80,11 +80,22 @@
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        getDisplay().getRealSize(mSizePoint);
+
         // Since we control our own bottom, be whatever size we want.
         // Otherwise the QSPanel ends up with 0 height when the window is only the
         // size of the status bar.
-        mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(
-                MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.UNSPECIFIED));
+        Configuration config = getResources().getConfiguration();
+        boolean navBelow = config.smallestScreenWidthDp >= 600
+                || config.orientation != Configuration.ORIENTATION_LANDSCAPE;
+        MarginLayoutParams params = (MarginLayoutParams) mQSPanel.getLayoutParams();
+        int maxQs = mSizePoint.y - params.topMargin - params.bottomMargin - getPaddingBottom()
+                - getResources().getDimensionPixelSize(R.dimen.qs_notif_collapsed_space);
+        if (navBelow) {
+            maxQs -= getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
+        }
+        mQSPanel.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(maxQs, MeasureSpec.AT_MOST));
+
         int width = mQSPanel.getMeasuredWidth();
         LayoutParams layoutParams = (LayoutParams) mQSPanel.getLayoutParams();
         int height = layoutParams.topMargin + layoutParams.bottomMargin
@@ -94,7 +105,6 @@
 
         // QSCustomizer will always be the height of the screen, but do this after
         // other measuring to avoid changing the height of the QS.
-        getDisplay().getRealSize(mSizePoint);
         mQSCustomizer.measure(widthMeasureSpec,
                 MeasureSpec.makeMeasureSpec(mSizePoint.y, MeasureSpec.EXACTLY));
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index cdf0c0f..29a8f16 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -25,6 +25,7 @@
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.ViewGroup;
@@ -207,6 +208,11 @@
     }
 
     @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        return isCustomizing() || mQSPanel.onInterceptTouchEvent(event);
+    }
+
+    @Override
     public void setHeaderClickable(boolean clickable) {
         if (DEBUG) Log.d(TAG, "setHeaderClickable " + clickable);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 8f41084..00b6c1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -28,8 +28,8 @@
 import android.service.quicksettings.Tile;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
-import android.widget.ImageView;
 import android.widget.LinearLayout;
 
 import com.android.internal.logging.MetricsLogger;
@@ -62,11 +62,8 @@
     protected final ArrayList<TileRecord> mRecords = new ArrayList<TileRecord>();
     protected final View mBrightnessView;
     private final H mHandler = new H();
-    private final View mPageIndicator;
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
-    private int mPanelPaddingBottom;
-    private int mBrightnessPaddingTop;
     protected boolean mExpanded;
     protected boolean mListening;
 
@@ -77,6 +74,7 @@
     protected QSSecurityFooter mFooter;
     private boolean mGridContentVisible = true;
 
+    private QSScrollLayout mScrollLayout;
     protected QSTileLayout mTileLayout;
 
     private QSCustomizer mCustomizePanel;
@@ -95,18 +93,12 @@
 
         setOrientation(VERTICAL);
 
-        mBrightnessView = LayoutInflater.from(context).inflate(
-                R.layout.quick_settings_brightness_dialog, this, false);
-        addView(mBrightnessView);
-
-        setupTileLayout();
-
-        mPageIndicator = LayoutInflater.from(context).inflate(
-                R.layout.qs_page_indicator, this, false);
-        addView(mPageIndicator);
-        if (mTileLayout instanceof PagedTileLayout) {
-            ((PagedTileLayout) mTileLayout).setPageIndicator((PageIndicator) mPageIndicator);
-        }
+        mBrightnessView = LayoutInflater.from(mContext).inflate(
+            R.layout.quick_settings_brightness_dialog, this, false);
+        mTileLayout = new TileLayout(mContext);
+        mTileLayout.setListening(mListening);
+        mScrollLayout = new QSScrollLayout(mContext, mBrightnessView, (View) mTileLayout);
+        addView(mScrollLayout);
 
         addDivider();
 
@@ -131,17 +123,6 @@
         return mDivider;
     }
 
-    public View getPageIndicator() {
-        return mPageIndicator;
-    }
-
-    protected void setupTileLayout() {
-        mTileLayout = (QSTileLayout) LayoutInflater.from(mContext).inflate(
-                R.layout.qs_paged_tile_layout, this, false);
-        mTileLayout.setListening(mListening);
-        addView((View) mTileLayout);
-    }
-
     public boolean isShowingCustomize() {
         return mCustomizePanel != null && mCustomizePanel.isCustomizing();
     }
@@ -241,9 +222,13 @@
 
     public void updateResources() {
         final Resources res = mContext.getResources();
-        mPanelPaddingBottom = res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom);
-        mBrightnessPaddingTop = res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top);
-        setPadding(0, mBrightnessPaddingTop, 0, mPanelPaddingBottom);
+        mBrightnessView.setPadding(
+            mBrightnessView.getPaddingLeft(),
+            res.getDimensionPixelSize(R.dimen.qs_brightness_padding_top),
+            mBrightnessView.getPaddingRight(),
+            mBrightnessView.getPaddingBottom());
+        setPadding(
+            0, 0, 0, res.getDimensionPixelSize(R.dimen.qs_panel_padding_bottom));
         for (TileRecord r : mRecords) {
             r.tile.clearState();
         }
@@ -282,8 +267,11 @@
     public void setExpanded(boolean expanded) {
         if (mExpanded == expanded) return;
         mExpanded = expanded;
-        if (!mExpanded && mTileLayout instanceof PagedTileLayout) {
-            ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
+        if (!mExpanded) {
+            if (mTileLayout instanceof PagedTileLayout) {
+                ((PagedTileLayout) mTileLayout).setCurrentItem(0, false);
+            }
+            mScrollLayout.setScrollY(0);
         }
         mMetricsLogger.visibility(MetricsEvent.QS_PANEL, mExpanded);
         if (!mExpanded) {
@@ -317,6 +305,7 @@
     }
 
     public void refreshAllTiles() {
+        mBrightnessController.checkRestrictionAndSetEnabled();
         for (TileRecord r : mRecords) {
             r.tile.refreshState();
         }
@@ -564,6 +553,11 @@
         mFooter.showDeviceMonitoringDialog();
     }
 
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        return mExpanded && mScrollLayout.shouldIntercept(event);
+    }
+
     private class H extends Handler {
         private static final int SHOW_DETAIL = 1;
         private static final int SET_TILE_VISIBILITY = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
new file mode 100644
index 0000000..9a74787
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSScrollLayout.java
@@ -0,0 +1,83 @@
+/*
+ * 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.qs;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.v4.widget.NestedScrollView;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+import android.widget.LinearLayout;
+
+/**
+ * Quick setting scroll view containing the brightness slider and the QS tiles.
+ *
+ * <p>Call {@link #shouldIntercept(MotionEvent)} from parent views'
+ * {@link #onInterceptTouchEvent(MotionEvent)} method to determine whether this view should
+ * consume the touch event.
+ */
+public class QSScrollLayout extends NestedScrollView {
+    private final int mTouchSlop;
+    private int mLastMotionY;
+    private Rect mHitRect = new Rect();
+
+    public QSScrollLayout(Context context, View... children) {
+        super(context);
+        mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+        LinearLayout linearLayout = new LinearLayout(mContext);
+        linearLayout.setLayoutParams(new LinearLayout.LayoutParams(
+            LinearLayout.LayoutParams.MATCH_PARENT,
+            LinearLayout.LayoutParams.WRAP_CONTENT));
+        linearLayout.setOrientation(LinearLayout.VERTICAL);
+        for (View view : children) {
+            linearLayout.addView(view);
+        }
+        addView(linearLayout);
+    }
+
+    public boolean shouldIntercept(MotionEvent ev) {
+        getHitRect(mHitRect);
+        if (!mHitRect.contains((int) ev.getX(), (int) ev.getY())) {
+            // Do not intercept touches that are not within this view's bounds.
+            return false;
+        }
+        if (ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
+            mLastMotionY = (int) ev.getY();
+        } else if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
+            // Do not allow NotificationPanelView to intercept touch events when this
+            // view can be scrolled down.
+            if (mLastMotionY >= 0 && Math.abs(ev.getY() - mLastMotionY) > mTouchSlop
+                    && canScrollVertically(1)) {
+                requestParentDisallowInterceptTouchEvent(true);
+                mLastMotionY = (int) ev.getY();
+                return true;
+            }
+        } else if (ev.getActionMasked() == MotionEvent.ACTION_CANCEL
+            || ev.getActionMasked() == MotionEvent.ACTION_UP) {
+            mLastMotionY = -1;
+            requestParentDisallowInterceptTouchEvent(false);
+        }
+        return false;
+    }
+
+    private void requestParentDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        final ViewParent parent = getParent();
+        if (parent != null) {
+            parent.requestDisallowInterceptTouchEvent(disallowIntercept);
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index 947b23f..8314855 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -20,6 +20,7 @@
 import android.util.AttributeSet;
 import android.view.Gravity;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.LinearLayout;
 import android.widget.Space;
 
@@ -50,13 +51,14 @@
     public QuickQSPanel(Context context, AttributeSet attrs) {
         super(context, attrs);
         if (mFooter != null) {
-            removeView((View) mFooter.getView());
+            removeView(mFooter.getView());
         }
         if (mTileLayout != null) {
             for (int i = 0; i < mRecords.size(); i++) {
                 mTileLayout.removeTile(mRecords.get(i));
             }
-            removeView((View) mTileLayout);
+            View tileLayoutView = (View) mTileLayout;
+            ((ViewGroup) tileLayoutView.getParent()).removeView(tileLayoutView);
         }
         mTileLayout = new HeaderTileLayout(context);
         mTileLayout.setListening(mListening);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 2ee66d8..eb2e519 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -19,6 +19,9 @@
 import static android.provider.Settings.Global.ZEN_MODE_ALARMS;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 
+import android.app.AlarmManager;
+import android.app.AlarmManager.AlarmClockInfo;
+import android.app.Dialog;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -27,9 +30,11 @@
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.net.Uri;
 import android.os.UserManager;
 import android.provider.Settings;
 import android.provider.Settings.Global;
+import android.service.notification.ScheduleCalendar;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenModeConfig.ZenRule;
 import android.service.quicksettings.Tile;
@@ -38,11 +43,13 @@
 import android.view.View;
 import android.view.View.OnAttachStateChangeListener;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.widget.Switch;
 import android.widget.Toast;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.notification.EnableZenModeDialog;
 import com.android.systemui.Dependency;
 import com.android.systemui.Prefs;
 import com.android.systemui.R;
@@ -53,6 +60,7 @@
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.volume.ZenModePanel;
 
@@ -129,14 +137,28 @@
 
     @Override
     protected void handleClick() {
+        // Zen is currently on
         if (mState.value) {
             mController.setZen(ZEN_MODE_OFF, null, TAG);
         } else {
-            mController.setZen(Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, TAG);
+            showDetail(true);
         }
     }
 
     @Override
+    public void showDetail(boolean show) {
+        mUiHandler.post(() -> {
+            Dialog mDialog = new EnableZenModeDialog(mContext).createDialog();
+            mDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG);
+            SystemUIDialog.setShowForAllUsers(mDialog, true);
+            SystemUIDialog.registerDismissListener(mDialog);
+            SystemUIDialog.setWindowOnTop(mDialog);
+            mUiHandler.post(() -> mDialog.show());
+            mHost.collapsePanels();
+        });
+    }
+
+    @Override
     protected void handleSecondaryClick() {
         if (mController.isVolumeRestricted()) {
             // Collapse the panels, so the user can see the toast.
@@ -178,6 +200,7 @@
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
         state.slash.isSlashed = !state.value;
         state.label = getTileLabel();
+        state.secondaryLabel = getSecondaryLabel(zen != Global.ZEN_MODE_OFF);
         checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
         switch (zen) {
             case Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS:
@@ -209,6 +232,102 @@
         state.expandedAccessibilityClassName = Switch.class.getName();
     }
 
+    /**
+     * Returns the secondary label to use for the given instance of do not disturb.
+     * - If turned on manually and end time is known, returns end time.
+     * - If turned on by an automatic rule, returns the automatic rule name.
+     * - If on due to an app, returns the app name.
+     * - If there's a combination of rules/apps that trigger, then shows the one that will
+     *  last the longest if applicable.
+     * @return null if do not disturb is off.
+     */
+    private String getSecondaryLabel(boolean zenOn) {
+        if (!zenOn) {
+            return null;
+        }
+
+        ZenModeConfig config = mController.getConfig();
+        String secondaryText = "";
+        long latestEndTime = -1;
+
+        // DND turned on by manual rule
+        if (config.manualRule != null) {
+            final Uri id = config.manualRule.conditionId;
+            if (config.manualRule.enabler != null) {
+                // app triggered manual rule
+                String appName = ZenModeConfig.getOwnerCaption(mContext, config.manualRule.enabler);
+                if (!appName.isEmpty()) {
+                    secondaryText = appName;
+                }
+            } else {
+                if (id == null) {
+                    // Do not disturb manually triggered to remain on forever until turned off
+                    // No subtext
+                    return null;
+                } else {
+                    latestEndTime = ZenModeConfig.tryParseCountdownConditionId(id);
+                    if (latestEndTime > 0) {
+                        final CharSequence formattedTime = ZenModeConfig.getFormattedTime(mContext,
+                                latestEndTime, ZenModeConfig.isToday(latestEndTime),
+                                mContext.getUserId());
+                        secondaryText = mContext.getString(R.string.qs_dnd_until, formattedTime);
+                    }
+                }
+            }
+        }
+
+        // DND turned on by an automatic rule
+        for (ZenModeConfig.ZenRule automaticRule : config.automaticRules.values()) {
+            if (automaticRule.isAutomaticActive()) {
+                if (ZenModeConfig.isValidEventConditionId(automaticRule.conditionId) ||
+                        ZenModeConfig.isValidScheduleConditionId(automaticRule.conditionId)) {
+                    // set text if automatic rule end time is the latest active rule end time
+                    long endTime = parseAutomaticRuleEndTime(automaticRule.conditionId);
+                    if (endTime > latestEndTime) {
+                        latestEndTime = endTime;
+                        secondaryText = automaticRule.name;
+                    }
+                } else {
+                    // set text if 3rd party rule
+                    return automaticRule.name;
+                }
+            }
+        }
+
+        return !secondaryText.equals("") ? secondaryText : null;
+    }
+
+    private long parseAutomaticRuleEndTime(Uri id) {
+        if (ZenModeConfig.isValidEventConditionId(id)) {
+            // cannot look up end times for events
+            return Long.MAX_VALUE;
+        }
+
+        if (ZenModeConfig.isValidScheduleConditionId(id)) {
+            ScheduleCalendar schedule = ZenModeConfig.toScheduleCalendar(id);
+            long endTimeMs = schedule.getNextChangeTime(System.currentTimeMillis());
+
+            // check if automatic rule will end on next alarm
+            if (schedule.exitAtAlarm()) {
+                long nextAlarm = getNextAlarm(mContext);
+                schedule.maybeSetNextAlarm(System.currentTimeMillis(), nextAlarm);
+                if (schedule.shouldExitForAlarm(endTimeMs)) {
+                    return nextAlarm;
+                }
+            }
+
+            return endTimeMs;
+        }
+
+        return -1;
+    }
+
+    private long getNextAlarm(Context context) {
+        final AlarmManager alarms = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        final AlarmClockInfo info = alarms.getNextAlarmClock(mContext.getUserId());
+        return info != null ? info.getTriggerTime() : 0;
+    }
+
     @Override
     public int getMetricsCategory() {
         return MetricsEvent.QS_DND;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
index 6c553de..0494e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/SwipeUpOnboarding.java
@@ -150,6 +150,11 @@
     }
 
     public void onConnectedToLauncher(ComponentName launcherComponent) {
+        // TODO: re-enable this once we have the proper callback for when a swipe up was performed.
+        final boolean disableOnboarding = true;
+        if (disableOnboarding) {
+            return;
+        }
         mLauncherComponent = launcherComponent;
         boolean alreadyLearnedSwipeUpForRecents = Prefs.getBoolean(mContext,
                 Prefs.Key.HAS_SWIPED_UP_FOR_RECENTS, false);
diff --git a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
index 3db30fc..15e92f4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/BrightnessController.java
@@ -31,6 +31,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
@@ -39,6 +40,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.Dependency;
 
 import java.util.ArrayList;
@@ -384,6 +386,18 @@
         }
     }
 
+    public void checkRestrictionAndSetEnabled() {
+        mBackgroundHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                ((ToggleSliderView)mControl).setEnforcedAdmin(
+                        RestrictedLockUtils.checkIfRestrictionEnforced(mContext,
+                                UserManager.DISALLOW_CONFIG_BRIGHTNESS,
+                                mUserTracker.getCurrentUserId()));
+            }
+        });
+    }
+
     private void setMode(int mode) {
         Settings.System.putIntForUser(mContext.getContentResolver(),
                 Settings.System.SCREEN_BRIGHTNESS_MODE, mode,
diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
index 722aba5..8ed4c75 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSeekBar.java
@@ -17,14 +17,21 @@
 package com.android.systemui.settings;
 
 import android.content.Context;
+import android.content.Intent;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.SeekBar;
 
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.systemui.Dependency;
+import com.android.systemui.plugins.ActivityStarter;
+
 public class ToggleSeekBar extends SeekBar {
     private String mAccessibilityLabel;
 
+    private RestrictedLockUtils.EnforcedAdmin mEnforcedAdmin = null;
+
     public ToggleSeekBar(Context context) {
         super(context);
     }
@@ -39,6 +46,12 @@
 
     @Override
     public boolean onTouchEvent(MotionEvent event) {
+        if (mEnforcedAdmin != null) {
+            Intent intent = RestrictedLockUtils.getShowAdminSupportDetailsIntent(
+                    mContext, mEnforcedAdmin);
+            Dependency.get(ActivityStarter.class).postStartActivityDismissingKeyguard(intent, 0);
+            return true;
+        }
         if (!isEnabled()) {
             setEnabled(true);
         }
@@ -57,4 +70,8 @@
             info.setText(mAccessibilityLabel);
         }
     }
+
+    public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
+        mEnforcedAdmin = admin;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java
index 07b9ec2..90744a6 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/ToggleSliderView.java
@@ -29,6 +29,7 @@
 import android.widget.SeekBar.OnSeekBarChangeListener;
 import android.widget.TextView;
 
+import com.android.settingslib.RestrictedLockUtils;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.policy.BrightnessMirrorController;
 
@@ -95,6 +96,12 @@
         }
     }
 
+    public void setEnforcedAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
+        mToggle.setEnabled(admin == null);
+        mSlider.setEnabled(admin == null);
+        mSlider.setEnforcedAdmin(admin);
+    }
+
     public void setOnChangedListener(Listener l) {
         mListener = l;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
index e59c703..eb5619b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -122,7 +122,7 @@
     private Interpolator mCurrentAppearInterpolator;
     private Interpolator mCurrentAlphaInterpolator;
 
-    private NotificationBackgroundView mBackgroundNormal;
+    protected NotificationBackgroundView mBackgroundNormal;
     private NotificationBackgroundView mBackgroundDimmed;
     private ObjectAnimator mBackgroundAnimator;
     private RectF mAppearAnimationRect = new RectF();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 5f4854a..1056ecc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.NotificationInflater.InflationCallback;
 
 import android.animation.Animator;
@@ -37,6 +38,7 @@
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
+import android.util.MathUtils;
 import android.util.Property;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -67,6 +69,7 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.statusbar.NotificationGuts.GutsContent;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationInflater;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -75,6 +78,7 @@
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.stack.AmbientState;
 import com.android.systemui.statusbar.stack.AnimationProperties;
 import com.android.systemui.statusbar.stack.ExpandableViewState;
 import com.android.systemui.statusbar.stack.NotificationChildrenContainer;
@@ -113,6 +117,7 @@
     private int mNotificationMaxHeight;
     private int mNotificationAmbientHeight;
     private int mIncreasedPaddingBetweenElements;
+    private int mNotificationLaunchHeight;
     private boolean mMustStayOnScreen;
 
     /** Does this row contain layouts that can adapt to row expansion */
@@ -172,6 +177,7 @@
     private boolean mIsSystemChildExpanded;
     private boolean mIsPinned;
     private FalsingManager mFalsingManager;
+    private boolean mExpandAnimationRunning;
     private AboveShelfChangedListener mAboveShelfChangedListener;
     private HeadsUpManager mHeadsUpManager;
     private View mHelperButton;
@@ -270,6 +276,7 @@
     private float mTranslationWhenRemoved;
     private boolean mWasChildInGroupWhenRemoved;
     private int mNotificationColorAmbient;
+    private NotificationViewState mNotificationViewState;
 
     @Override
     public boolean isGroupExpansionChanging() {
@@ -363,6 +370,14 @@
         mNotificationInflater.inflateNotificationViews();
     }
 
+    @Override
+    public void setPressed(boolean pressed) {
+        if (isOnKeyguard() || mEntry.notification.getNotification().contentIntent == null) {
+            // We're dropping the ripple if we have a collapse / launch animation
+            super.setPressed(pressed);
+        }
+    }
+
     public void onNotificationUpdated() {
         for (NotificationContentView l : mLayouts) {
             l.onNotificationUpdated(mEntry);
@@ -1096,9 +1111,19 @@
         return mGroupParentWhenDismissed;
     }
 
-    public void performDismiss() {
-        if (mOnDismissRunnable != null) {
-            mOnDismissRunnable.run();
+    public void performDismiss(boolean fromAccessibility) {
+        if (mGroupManager.isOnlyChildInGroup(getStatusBarNotification())) {
+            ExpandableNotificationRow groupSummary =
+                    mGroupManager.getLogicalGroupSummary(getStatusBarNotification());
+            if (groupSummary.isClearable()) {
+                groupSummary.performDismiss(fromAccessibility);
+            }
+        }
+        setDismissed(true, fromAccessibility);
+        if (isClearable()) {
+            if (mOnDismissRunnable != null) {
+                mOnDismissRunnable.run();
+            }
         }
     }
 
@@ -1159,6 +1184,9 @@
     }
 
     private void updateContentTransformation() {
+        if (mExpandAnimationRunning) {
+            return;
+        }
         float contentAlpha;
         float translationY = -mContentTransformationAmount * mIconTransformContentShift;
         if (mIsLastChild) {
@@ -1440,6 +1468,11 @@
         mMenuRow.resetMenu();
     }
 
+    public CharSequence getActiveRemoteInputText() {
+        return mPrivateLayout.getActiveRemoteInputText();
+    }
+
+
     public void animateTranslateNotification(final float leftTarget) {
         if (mTranslateAnim != null) {
             mTranslateAnim.cancel();
@@ -1527,10 +1560,13 @@
     }
 
     private void updateChildrenVisibility() {
-        mPrivateLayout.setVisibility(!shouldShowPublic() && !mIsSummaryWithChildren ? VISIBLE
-                : INVISIBLE);
+        boolean hideContentWhileLaunching = mExpandAnimationRunning && mGuts != null
+                && mGuts.isExposed();
+        mPrivateLayout.setVisibility(!shouldShowPublic() && !mIsSummaryWithChildren
+                && !hideContentWhileLaunching ? VISIBLE : INVISIBLE);
         if (mChildrenContainer != null) {
-            mChildrenContainer.setVisibility(!shouldShowPublic() && mIsSummaryWithChildren ? VISIBLE
+            mChildrenContainer.setVisibility(!shouldShowPublic() && mIsSummaryWithChildren
+                    && !hideContentWhileLaunching ? VISIBLE
                     : INVISIBLE);
         }
         // The limits might have changed if the view suddenly became a group or vice versa
@@ -1569,6 +1605,62 @@
         updateShelfIconColor();
     }
 
+    public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+        if (params == null) {
+            return;
+        }
+        setTranslationY(params.getTop());
+        float zProgress = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+                params.getProgress(0, 50));
+        float translationZ = MathUtils.lerp(params.getStartTranslationZ(),
+                mNotificationLaunchHeight,
+                zProgress);
+        setTranslationZ(translationZ);
+        setActualHeight(params.getHeight());
+        mBackgroundNormal.setExpandAnimationParams(params);
+    }
+
+    public void setExpandAnimationRunning(boolean expandAnimationRunning) {
+        if (expandAnimationRunning) {
+            View contentView;
+            if (mIsSummaryWithChildren) {
+                contentView =  mChildrenContainer;
+            } else {
+                contentView = getShowingLayout();
+            }
+            if (mGuts != null && mGuts.isExposed()) {
+                contentView = mGuts;
+            }
+            contentView.animate()
+                    .alpha(0f)
+                    .setDuration(ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT)
+                    .setInterpolator(Interpolators.ALPHA_OUT);
+            setAboveShelf(true);
+            mExpandAnimationRunning = true;
+            mNotificationViewState.cancelAnimations(this);
+            mNotificationLaunchHeight = AmbientState.getNotificationLaunchHeight(getContext());
+        } else {
+            mExpandAnimationRunning = false;
+            setAboveShelf(isAboveShelf());
+            if (mGuts != null) {
+                mGuts.setAlpha(1.0f);
+            }
+        }
+        updateChildrenVisibility();
+        updateClipping();
+        mBackgroundNormal.setExpandAnimationRunning(expandAnimationRunning);
+    }
+
+    @Override
+    protected boolean shouldClipToActualHeight() {
+        return super.shouldClipToActualHeight() && !mExpandAnimationRunning;
+    }
+
+    @Override
+    public boolean isExpandAnimationRunning() {
+        return mExpandAnimationRunning;
+    }
+
     /**
      * Tap sounds should not be played when we're unlocking.
      * Doing so would cause audio collision and the system would feel unpolished.
@@ -2142,6 +2234,9 @@
 
     @Override
     public void setClipBottomAmount(int clipBottomAmount) {
+        if (mExpandAnimationRunning) {
+            return;
+        }
         if (clipBottomAmount != mClipBottomAmount) {
             super.setClipBottomAmount(clipBottomAmount);
             for (NotificationContentView l : mLayouts) {
@@ -2328,8 +2423,7 @@
         }
         switch (action) {
             case AccessibilityNodeInfo.ACTION_DISMISS:
-                NotificationStackScrollLayout.performDismiss(this, mGroupManager,
-                        true /* fromAccessibility */);
+                performDismiss(true /* fromAccessibility */);
                 return true;
             case AccessibilityNodeInfo.ACTION_COLLAPSE:
             case AccessibilityNodeInfo.ACTION_EXPAND:
@@ -2352,13 +2446,15 @@
 
     @Override
     public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
-        return new NotificationViewState(stackScrollState);
+        mNotificationViewState = new NotificationViewState(stackScrollState);
+        return mNotificationViewState;
     }
 
     @Override
     public boolean isAboveShelf() {
         return !isOnKeyguard()
-                && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf));
+                && (mIsPinned || mHeadsupDisappearRunning || (mIsHeadsUp && mAboveShelf)
+                || mExpandAnimationRunning);
     }
 
     public void setShowAmbient(boolean showAmbient) {
@@ -2444,9 +2540,12 @@
 
         @Override
         public void applyToView(View view) {
-            super.applyToView(view);
             if (view instanceof ExpandableNotificationRow) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+                if (row.isExpandAnimationRunning()) {
+                    return;
+                }
+                super.applyToView(view);
                 row.applyChildrenState(mOverallState);
             }
         }
@@ -2464,9 +2563,12 @@
 
         @Override
         public void animateTo(View child, AnimationProperties properties) {
-            super.animateTo(child, properties);
             if (child instanceof ExpandableNotificationRow) {
                 ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+                if (row.isExpandAnimationRunning()) {
+                    return;
+                }
+                super.animateTo(child, properties);
                 row.startChildAnimation(mOverallState, properties);
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
index eafa825..1496a41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ExpandableView.java
@@ -151,6 +151,10 @@
         return mActualHeight;
     }
 
+    public boolean isExpandAnimationRunning() {
+        return false;
+    }
+
     /**
      * @return The maximum height of this notification.
      */
@@ -375,8 +379,8 @@
         return false;
     }
 
-    private void updateClipping() {
-        if (mClipToActualHeight) {
+    protected void updateClipping() {
+        if (mClipToActualHeight && shouldClipToActualHeight()) {
             int top = getClipTopAmount();
             mClipRect.set(0, top, getWidth(), Math.max(getActualHeight() + getExtraBottomPadding()
                     - mClipBottomAmount, top));
@@ -386,6 +390,10 @@
         }
     }
 
+    protected boolean shouldClipToActualHeight() {
+        return true;
+    }
+
     public void setClipToActualHeight(boolean clipToActualHeight) {
         mClipToActualHeight = clipToActualHeight;
         updateClipping();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
index 45b35d0..d6beb7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -20,6 +20,7 @@
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
 import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.graphics.drawable.LayerDrawable;
@@ -27,7 +28,9 @@
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.systemui.Interpolators;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 
 /**
  * A view that can be used for both the dimmed and normal background of an notification.
@@ -44,6 +47,9 @@
     private boolean mBottomIsRounded;
     private int mBackgroundTop;
     private boolean mBottomAmountClips = true;
+    private boolean mExpandAnimationRunning;
+    private float mActualWidth;
+    private int mDrawableAlpha = 255;
 
     public NotificationBackgroundView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -53,9 +59,12 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop) {
+        if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop
+                || mExpandAnimationRunning) {
             canvas.save();
-            canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+            if (!mExpandAnimationRunning) {
+                canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+            }
             draw(canvas, mBackground);
             canvas.restore();
         }
@@ -64,10 +73,16 @@
     private void draw(Canvas canvas, Drawable drawable) {
         if (drawable != null) {
             int bottom = mActualHeight;
-            if (mBottomIsRounded && mBottomAmountClips) {
+            if (mBottomIsRounded && mBottomAmountClips && !mExpandAnimationRunning) {
                 bottom -= mClipBottomAmount;
             }
-            drawable.setBounds(0, mBackgroundTop, getWidth(), bottom);
+            int left = 0;
+            int right = getWidth();
+            if (mExpandAnimationRunning) {
+                left = (int) ((getWidth() - mActualWidth) / 2.0f);
+                right = (int) (left + mActualWidth);
+            }
+            drawable.setBounds(left, mBackgroundTop, right, bottom);
             drawable.draw(canvas);
         }
     }
@@ -133,6 +148,9 @@
     }
 
     public void setActualHeight(int actualHeight) {
+        if (mExpandAnimationRunning) {
+            return;
+        }
         mActualHeight = actualHeight;
         invalidate();
     }
@@ -170,6 +188,10 @@
     }
 
     public void setDrawableAlpha(int drawableAlpha) {
+        mDrawableAlpha = drawableAlpha;
+        if (mExpandAnimationRunning) {
+            return;
+        }
         mBackground.setAlpha(drawableAlpha);
     }
 
@@ -208,4 +230,29 @@
         mBackgroundTop = backgroundTop;
         invalidate();
     }
+
+    public void setExpandAnimationParams(ActivityLaunchAnimator.ExpandAnimationParameters params) {
+        mActualHeight = params.getHeight();
+        mActualWidth = params.getWidth();
+        float alphaProgress = Interpolators.ALPHA_IN.getInterpolation(
+                params.getProgress(
+                        ActivityLaunchAnimator.ANIMATION_DURATION_FADE_CONTENT /* delay */,
+                        ActivityLaunchAnimator.ANIMATION_DURATION_FADE_APP /* duration */));
+        mBackground.setAlpha((int) (mDrawableAlpha * (1.0f - alphaProgress)));
+        invalidate();
+    }
+
+    public void setExpandAnimationRunning(boolean running) {
+        mExpandAnimationRunning = running;
+        if (mBackground instanceof LayerDrawable) {
+            GradientDrawable gradientDrawable =
+                    (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
+            gradientDrawable.setXfermode(
+                    running ? new PorterDuffXfermode(PorterDuff.Mode.SRC) : null);
+        }
+        if (!mExpandAnimationRunning) {
+            setDrawableAlpha(mDrawableAlpha);
+        }
+        invalidate();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
index a4c17e3..e811ed3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationContentView.java
@@ -1563,4 +1563,14 @@
         }
         return visibleWrapper.shouldClipToRounding(topRounded, bottomRounded);
     }
+
+    public CharSequence getActiveRemoteInputText() {
+        if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) {
+            return mExpandedRemoteInput.getText();
+        }
+        if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) {
+            return mHeadsUpRemoteInput.getText();
+        }
+        return null;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
index 9d8892d..1aaa3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -15,6 +15,9 @@
  */
 package com.android.systemui.statusbar;
 
+import static android.service.notification.NotificationListenerService.Ranking
+        .USER_SENTIMENT_NEGATIVE;
+
 import android.app.INotificationManager;
 import android.app.NotificationChannel;
 import android.content.Context;
@@ -41,7 +44,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -133,14 +135,14 @@
      * channel.
      */
     private void startAppNotificationSettingsActivity(String packageName, final int appUid,
-            final NotificationChannel channel) {
+            final NotificationChannel channel, ExpandableNotificationRow row) {
         final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
         intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
         intent.putExtra(Settings.EXTRA_APP_UID, appUid);
         if (channel != null) {
             intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId());
         }
-        mPresenter.startNotificationGutsIntent(intent, appUid);
+        mPresenter.startNotificationGutsIntent(intent, appUid, row);
     }
 
     public void bindGuts(final ExpandableNotificationRow row) {
@@ -198,14 +200,14 @@
                     mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
                     guts.resetFalsingCheck();
                     mOnSettingsClickListener.onClick(sbn.getKey());
-                    startAppNotificationSettingsActivity(pkg, appUid, channel);
+                    startAppNotificationSettingsActivity(pkg, appUid, channel, row);
                 };
             }
             final NotificationInfo.OnAppSettingsClickListener onAppSettingsClick = (View v,
                     Intent intent) -> {
                 mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
                 guts.resetFalsingCheck();
-                mPresenter.startNotificationGutsIntent(intent, sbn.getUid());
+                mPresenter.startNotificationGutsIntent(intent, sbn.getUid(), row);
             };
             final View.OnClickListener onDoneClick = (View v) -> {
                 saveAndCloseNotificationMenu(row, guts, v);
@@ -231,7 +233,8 @@
             try {
                 info.bindNotification(pmUser, iNotificationManager, pkg, row.getEntry().channel,
                         channels.size(), sbn, mCheckSaveListener, onSettingsClick,
-                        onAppSettingsClick, mNonBlockablePkgs);
+                        onAppSettingsClick, mNonBlockablePkgs,
+                        row.getEntry().userSentiment == USER_SENTIMENT_NEGATIVE);
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
index 6279fdc..735f4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java
@@ -79,6 +79,7 @@
     private OnSettingsClickListener mOnSettingsClickListener;
     private OnAppSettingsClickListener mAppSettingsClickListener;
     private NotificationGuts mGutsContainer;
+    private boolean mNegativeUserSentiment;
 
     private OnClickListener mOnKeepShowing = v -> {
         closeControls(v);
@@ -122,6 +123,22 @@
             final OnAppSettingsClickListener onAppSettingsClick,
             final Set<String> nonBlockablePkgs)
             throws RemoteException {
+        bindNotification(pm, iNotificationManager, pkg, notificationChannel, numChannels, sbn,
+                checkSaveListener, onSettingsClick, onAppSettingsClick, nonBlockablePkgs,
+                false /* negative sentiment */);
+    }
+
+    public void bindNotification(final PackageManager pm,
+            final INotificationManager iNotificationManager,
+            final String pkg,
+            final NotificationChannel notificationChannel,
+            final int numChannels,
+            final StatusBarNotification sbn,
+            final CheckSaveListener checkSaveListener,
+            final OnSettingsClickListener onSettingsClick,
+            final OnAppSettingsClickListener onAppSettingsClick,
+            final Set<String> nonBlockablePkgs,
+            boolean negativeUserSentiment)  throws RemoteException {
         mINotificationManager = iNotificationManager;
         mPkg = pkg;
         mNumNotificationChannels = numChannels;
@@ -133,6 +150,7 @@
         mOnSettingsClickListener = onSettingsClick;
         mSingleNotificationChannel = notificationChannel;
         mStartingUserImportance = mChosenImportance = mSingleNotificationChannel.getImportance();
+        mNegativeUserSentiment = negativeUserSentiment;
 
         int numTotalChannels = mINotificationManager.getNumNotificationChannelsForPackage(
                 pkg, mAppUid, false /* includeDeleted */);
@@ -227,27 +245,30 @@
     }
 
     private void bindPrompt() {
-        final TextView channelName = findViewById(R.id.channel_name);
         final TextView blockPrompt = findViewById(R.id.block_prompt);
+        bindName();
         if (mNonblockable) {
-            if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) {
-                channelName.setVisibility(View.GONE);
-            } else {
-                channelName.setText(mSingleNotificationChannel.getName());
-            }
-
             blockPrompt.setText(R.string.notification_unblockable_desc);
         } else {
-            if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) {
-                channelName.setVisibility(View.GONE);
+            if (mNegativeUserSentiment) {
+                blockPrompt.setText(R.string.inline_blocking_helper);
+            }  else if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) {
                 blockPrompt.setText(R.string.inline_keep_showing_app);
             } else {
-                channelName.setText(mSingleNotificationChannel.getName());
                 blockPrompt.setText(R.string.inline_keep_showing);
             }
         }
     }
 
+    private void bindName() {
+        final TextView channelName = findViewById(R.id.channel_name);
+        if (mIsSingleDefaultChannel || mNumNotificationChannels > 1) {
+            channelName.setVisibility(View.GONE);
+        } else {
+            channelName.setText(mSingleNotificationChannel.getName());
+        }
+    }
+
     private boolean hasImportanceChanged() {
         return mSingleNotificationChannel != null && mStartingUserImportance != mChosenImportance;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
index 43be44d..0c19ec0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationListContainer.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar;
 
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -179,4 +181,11 @@
      * @return true if has pulsing notifications
      */
     boolean hasPulsingNotifications();
+
+    /**
+     * Apply parameters of the expand animation to the layout
+     */
+    default void applyExpandAnimationParams(ExpandAnimationParameters params) {}
+
+    default void setExpandingNotification(ExpandableNotificationRow row) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
index 12641a0..5263bb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationPresenter.java
@@ -16,9 +16,7 @@
 package com.android.systemui.statusbar;
 
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.os.Handler;
-import android.service.notification.StatusBarNotification;
 import android.view.View;
 
 /**
@@ -48,7 +46,7 @@
      * Runs the given intent. The presenter may want to run some animations or close itself when
      * this happens.
      */
-    void startNotificationGutsIntent(Intent intent, int appUid);
+    void startNotificationGutsIntent(Intent intent, int appUid, ExpandableNotificationRow row);
 
     /**
      * Returns the Handler for NotificationPresenter.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index 266c09b..cd4c7ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.service.notification.NotificationListenerService;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -338,6 +339,9 @@
                     stack.push(notificationChildren.get(i));
                 }
             }
+
+            row.showBlockingHelper(entry.userSentiment ==
+                    NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE);
         }
 
         mPresenter.onUpdateRowStates();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
index 97e3d22..cfc69a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/RemoteInputController.java
@@ -47,7 +47,6 @@
     private final Delegate mDelegate;
 
     public RemoteInputController(Delegate delegate) {
-        addCallback(Dependency.get(StatusBarWindowManager.class));
         mDelegate = delegate;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
new file mode 100644
index 0000000..8336d29
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -0,0 +1,242 @@
+/*
+ * 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.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityOptions;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.MathUtils;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.ViewRootImpl;
+
+import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.NotificationListContainer;
+import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.StatusBarWindowView;
+import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
+
+import java.util.function.Consumer;
+
+/**
+ * A class that allows activities to be launched in a seamless way where the notification
+ * transforms nicely into the starting window.
+ */
+public class ActivityLaunchAnimator {
+
+    private static final int ANIMATION_DURATION = 400;
+    public static final long ANIMATION_DURATION_FADE_CONTENT = 67;
+    public static final long ANIMATION_DURATION_FADE_APP = 200;
+    public static final long ANIMATION_DELAY_ICON_FADE_IN = ANIMATION_DURATION -
+            CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY
+            - 16;
+    private final NotificationPanelView mNotificationPanel;
+    private final NotificationListContainer mNotificationContainer;
+    private final StatusBarWindowView mStatusBarWindow;
+    private final Consumer<Boolean> mPanelCollapser;
+
+    public ActivityLaunchAnimator(StatusBarWindowView statusBarWindow,
+            Consumer<Boolean> panelCollapser,
+            NotificationPanelView notificationPanel,
+            NotificationListContainer container) {
+        mNotificationPanel = notificationPanel;
+        mNotificationContainer = container;
+        mStatusBarWindow = statusBarWindow;
+        mPanelCollapser = panelCollapser;
+    }
+
+    public ActivityOptions getLaunchAnimation(
+            ExpandableNotificationRow sourceNofitication) {
+        AnimationRunner animationRunner = new AnimationRunner(sourceNofitication);
+        return ActivityOptions.makeRemoteAnimation(
+                new RemoteAnimationAdapter(animationRunner, 1000 /* Duration */, 0 /* delay */));
+    }
+
+    class AnimationRunner extends IRemoteAnimationRunner.Stub {
+
+        private final ExpandableNotificationRow mSourceNotification;
+        private final ExpandAnimationParameters mParams;
+        private final Rect mWindowCrop = new Rect();
+        private boolean mLeashShown;
+        private boolean mInstantCollapsePanel = true;
+
+        public AnimationRunner(ExpandableNotificationRow sourceNofitication) {
+            mSourceNotification = sourceNofitication;
+            mParams = new ExpandAnimationParameters();
+        }
+
+        @Override
+        public void onAnimationStart(RemoteAnimationTarget[] remoteAnimationTargets,
+                IRemoteAnimationFinishedCallback iRemoteAnimationFinishedCallback)
+                    throws RemoteException {
+            mSourceNotification.post(() -> {
+                boolean first = true;
+                for (RemoteAnimationTarget app : remoteAnimationTargets) {
+                    if (app.mode == RemoteAnimationTarget.MODE_OPENING) {
+                        setExpandAnimationRunning(true);
+                        mInstantCollapsePanel = app.position.y == 0
+                                && app.sourceContainerBounds.height()
+                                        >= mNotificationPanel.getHeight();
+                        if (!mInstantCollapsePanel) {
+                            mNotificationPanel.collapseWithDuration(ANIMATION_DURATION);
+                        }
+                        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+                        mParams.startPosition = mSourceNotification.getLocationOnScreen();
+                        mParams.startTranslationZ = mSourceNotification.getTranslationZ();
+                        int targetWidth = app.sourceContainerBounds.width();
+                        int notificationHeight = mSourceNotification.getActualHeight();
+                        int notificationWidth = mSourceNotification.getWidth();
+                        anim.setDuration(ANIMATION_DURATION);
+                        anim.setInterpolator(Interpolators.LINEAR);
+                        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                            @Override
+                            public void onAnimationUpdate(ValueAnimator animation) {
+                                mParams.linearProgress = animation.getAnimatedFraction();
+                                float progress
+                                        = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
+                                                mParams.linearProgress);
+                                int newWidth = (int) MathUtils.lerp(notificationWidth,
+                                        targetWidth, progress);
+                                mParams.left = (int) ((targetWidth - newWidth) / 2.0f);
+                                mParams.right = mParams.left + newWidth;
+                                mParams.top = (int) MathUtils.lerp(mParams.startPosition[1],
+                                        app.position.y, progress);
+                                mParams.bottom = (int) MathUtils.lerp(mParams.startPosition[1]
+                                                + notificationHeight,
+                                        app.position.y + app.sourceContainerBounds.bottom,
+                                        progress);
+                                applyParamsToWindow(app);
+                                applyParamsToNotification(mParams);
+                                applyParamsToNotificationList(mParams);
+                            }
+                        });
+                        anim.addListener(new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                setExpandAnimationRunning(false);
+                                if (mInstantCollapsePanel) {
+                                    mPanelCollapser.accept(false /* animate */);
+                                }
+                                try {
+                                    iRemoteAnimationFinishedCallback.onAnimationFinished();
+                                } catch (RemoteException e) {
+                                    e.printStackTrace();
+                                }
+                            }
+                        });
+                        anim.start();
+                        break;
+                    }
+                }
+            });
+        }
+
+        private void setExpandAnimationRunning(boolean running) {
+            mNotificationPanel.setLaunchingNotification(running);
+            mSourceNotification.setExpandAnimationRunning(running);
+            mStatusBarWindow.setExpandAnimationRunning(running);
+            mNotificationContainer.setExpandingNotification(running ? mSourceNotification : null);
+            if (!running) {
+                applyParamsToNotification(null);
+                applyParamsToNotificationList(null);
+            }
+
+        }
+
+        private void applyParamsToNotificationList(ExpandAnimationParameters params) {
+            mNotificationContainer.applyExpandAnimationParams(params);
+            mNotificationPanel.applyExpandAnimationParams(params);
+        }
+
+        private void applyParamsToNotification(ExpandAnimationParameters params) {
+            mSourceNotification.applyExpandAnimationParams(params);
+        }
+
+        private void applyParamsToWindow(RemoteAnimationTarget app) {
+            SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+            if (!mLeashShown) {
+                t.show(app.leash);
+                mLeashShown = true;
+            }
+            Matrix m = new Matrix();
+            m.postTranslate(0, (float) (mParams.top - app.position.y));
+            t.setMatrix(app.leash, m, new float[9]);
+            mWindowCrop.set(mParams.left, 0, mParams.right, mParams.getHeight());
+            t.setWindowCrop(app.leash, mWindowCrop);
+            ViewRootImpl viewRootImpl = mSourceNotification.getViewRootImpl();
+            if (viewRootImpl != null) {
+                Surface systemUiSurface = viewRootImpl.mSurface;
+                t.deferTransactionUntilSurface(app.leash, systemUiSurface,
+                        systemUiSurface.getNextFrameNumber());
+            }
+            t.apply();
+        }
+
+        @Override
+        public void onAnimationCancelled() throws RemoteException {
+        }
+    };
+
+    public static class ExpandAnimationParameters {
+        float linearProgress;
+        int[] startPosition;
+        float startTranslationZ;
+        int left;
+        int top;
+        int right;
+        int bottom;
+
+        public ExpandAnimationParameters() {
+        }
+
+        public int getTop() {
+            return top;
+        }
+
+        public int getWidth() {
+            return right - left;
+        }
+
+        public int getHeight() {
+            return bottom - top;
+        }
+
+        public int getTopChange() {
+            return Math.min(top - startPosition[1], 0);
+        }
+
+
+        public float getProgress(long delay, long duration) {
+            return MathUtils.constrain((linearProgress * ANIMATION_DURATION - delay)
+                    / duration, 0.0f, 1.0f);
+        }
+
+        public float getStartTranslationZ() {
+            return startTranslationZ;
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 61cb61c..f7f791e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -51,6 +51,8 @@
 
     public static final String TAG = "CollapsedStatusBarFragment";
     private static final String EXTRA_PANEL_STATE = "panel_state";
+    public static final int FADE_IN_DURATION = 320;
+    public static final int FADE_IN_DELAY = 50;
     private PhoneStatusBarView mStatusBar;
     private KeyguardMonitor mKeyguardMonitor;
     private NetworkController mNetworkController;
@@ -257,9 +259,9 @@
         }
         v.animate()
                 .alpha(1f)
-                .setDuration(320)
+                .setDuration(FADE_IN_DURATION)
                 .setInterpolator(Interpolators.ALPHA_IN)
-                .setStartDelay(50)
+                .setStartDelay(FADE_IN_DELAY)
 
                 // We need to clean up any pending end action from animateHide if we call
                 // both hide and show in the same frame before the animation actually gets started.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 66cb59e..b5101a6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
@@ -45,7 +47,6 @@
 import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.FrameLayout;
-
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.keyguard.KeyguardStatusView;
@@ -65,6 +66,7 @@
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
@@ -241,6 +243,8 @@
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private boolean mUserSetupComplete;
     private int mQsNotificationTopPadding;
+    private float mExpandOffset;
+    private boolean mHideIconsDuringNotificationLaunch = true;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -670,7 +674,7 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
-        if (mBlockTouches || mQs.isCustomizing()) {
+        if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
             return false;
         }
         initDownStates(event);
@@ -1675,8 +1679,9 @@
         if (mStatusBar.getBarState() == StatusBarState.KEYGUARD) {
             return 0;
         }
-        float translation = NotificationUtils.interpolate(-mQsMinExpansionHeight, 0,
-                mNotificationStackScroller.getAppearFraction(mExpandedHeight));
+        float translation = MathUtils.lerp(-mQsMinExpansionHeight, 0,
+                Math.min(1.0f, mNotificationStackScroller.getAppearFraction(mExpandedHeight)))
+                + mExpandOffset;
         return Math.min(0, translation);
     }
 
@@ -2540,6 +2545,9 @@
     }
 
     public boolean hideStatusBarIconsWhenExpanded() {
+        if (mLaunchingNotification) {
+            return mHideIconsDuringNotificationLaunch;
+        }
         return !isFullWidth() || !mShowIconsWhenExpanded;
     }
 
@@ -2665,4 +2673,19 @@
     public LockIcon getLockIcon() {
         return mKeyguardBottomArea.getLockIcon();
     }
+
+    public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+        mExpandOffset = params != null ? params.getTopChange() : 0;
+        updateQsExpansion();
+        if (params != null) {
+            boolean hideIcons = params.getProgress(
+                    ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
+            if (hideIcons != mHideIconsDuringNotificationLaunch) {
+                mHideIconsDuringNotificationLaunch = hideIcons;
+                if (!hideIcons) {
+                    mStatusBar.recomputeDisableFlags(true /* animate */);
+                }
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index 2fc22ca..a62a424 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -60,12 +60,15 @@
     public static final String TAG = PanelView.class.getSimpleName();
     private static final int INITIAL_OPENING_PEEK_DURATION = 200;
     private static final int PEEK_ANIMATION_DURATION = 360;
+    private static final int NO_FIXED_DURATION = -1;
     private long mDownTime;
     private float mMinExpandHeight;
     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     private boolean mPanelUpdateWhenAnimatorEnds;
     private boolean mVibrateOnOpening;
     private boolean mVibrationEnabled;
+    protected boolean mLaunchingNotification;
+    private int mFixedDuration = NO_FIXED_DURATION;
 
     private final void logf(String fmt, Object... args) {
         Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
@@ -785,6 +788,9 @@
             if (vel == 0) {
                 animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
             }
+            if (mFixedDuration != NO_FIXED_DURATION) {
+                animator.setDuration(mFixedDuration);
+            }
         }
         animator.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
@@ -1249,4 +1255,14 @@
     public void setHeadsUpManager(HeadsUpManager headsUpManager) {
         mHeadsUpManager = headsUpManager;
     }
+
+    public void setLaunchingNotification(boolean launchingNotification) {
+        mLaunchingNotification = launchingNotification;
+    }
+
+    public void collapseWithDuration(int animationDuration) {
+        mFixedDuration = animationDuration;
+        collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+        mFixedDuration = NO_FIXED_DURATION;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index d13ecae..7d84550 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -100,6 +100,8 @@
 import android.service.notification.StatusBarNotification;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
+import android.text.SpannedString;
+import android.text.TextUtils;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
@@ -205,6 +207,7 @@
 import com.android.systemui.statusbar.SignalClusterView;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.AboveShelfObserver;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.UnlockMethodCache.OnUnlockMethodChangedListener;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -586,6 +589,7 @@
 
     private NavigationBarFragment mNavigationBar;
     private View mNavigationBarView;
+    private ActivityLaunchAnimator mActivityLaunchAnimator;
 
     @Override
     public void start() {
@@ -755,6 +759,10 @@
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
+        mActivityLaunchAnimator = new ActivityLaunchAnimator(mStatusBarWindow,
+                this::collapsePanel,
+                mNotificationPanel,
+                mStackScroller);
         mGutsManager.setUpWithPresenter(this, mEntryManager, mStackScroller, mCheckSaveListener,
                 key -> {
                     try {
@@ -2760,6 +2768,7 @@
                         mStackScroller.requestDisallowDismiss();
                     }
                 });
+        mRemoteInputManager.getController().addCallback(mStatusBarWindowManager);
         mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
     }
 
@@ -2795,7 +2804,8 @@
             intent.setFlags(
                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
             int result = ActivityManager.START_CANCELED;
-            ActivityOptions options = new ActivityOptions(getActivityOptions());
+            ActivityOptions options = new ActivityOptions(getActivityOptions(
+                    null /* sourceNotification */));
             options.setDisallowEnterPictureInPictureWhileLaunching(
                     disallowEnterPictureInPictureWhileLaunching);
             if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
@@ -4910,6 +4920,7 @@
                     ActivityManager.getService().resumeAppSwitches();
                 } catch (RemoteException e) {
                 }
+                int launchResult = ActivityManager.START_CANCELED;
                 if (intent != null) {
                     // If we are launching a work activity and require to launch
                     // separate work challenge, we defer the activity action and cancel
@@ -4924,12 +4935,30 @@
                                     notificationKey)) {
                                 // Show work challenge, do not run PendingIntent and
                                 // remove notification
+                                collapsePanel();
                                 return;
                             }
                         }
                     }
+                    Intent fillInIntent = null;
+                    Entry entry = row.getEntry();
+                    CharSequence remoteInputText = null;
+                    RemoteInputController controller = mRemoteInputManager.getController();
+                    if (controller.isRemoteInputActive(entry)) {
+                        remoteInputText = row.getActiveRemoteInputText();
+                    }
+                    if (TextUtils.isEmpty(remoteInputText)
+                            && !TextUtils.isEmpty(entry.remoteInputText)) {
+                        remoteInputText = entry.remoteInputText;
+                    }
+                    if (!TextUtils.isEmpty(remoteInputText)
+                            && !controller.isSpinning(entry.key)) {
+                        fillInIntent = new Intent().putExtra(Notification.EXTRA_REMOTE_INPUT_DRAFT,
+                                remoteInputText.toString());
+                    }
                     try {
-                        intent.send(null, 0, null, null, null, null, getActivityOptions());
+                        launchResult = intent.sendAndReturnResult(mContext, 0, fillInIntent, null,
+                                null, null, getActivityOptions(row));
                     } catch (PendingIntent.CanceledException e) {
                         // the stack trace isn't very helpful here.
                         // Just log the exception message.
@@ -4941,6 +4970,13 @@
                         mAssistManager.hideAssist();
                     }
                 }
+                if (shouldCollapse(launchResult)) {
+                    if (Looper.getMainLooper().isCurrentThread()) {
+                        collapsePanel();
+                    } else {
+                        mStackScroller.post(this::collapsePanel);
+                    }
+                }
 
                 try {
                     mBarService.onNotificationClick(notificationKey);
@@ -4963,19 +4999,45 @@
                 new Thread(runnable).start();
             }
 
-            if (!mNotificationPanel.isFullyCollapsed()) {
-                // close the shade if it was open
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
-                        true /* delayed */);
-                visibilityChanged(false);
-
-                return true;
-            } else {
-                return false;
-            }
+            return !mNotificationPanel.isFullyCollapsed();
         }, afterKeyguardGone);
     }
 
+    private boolean shouldCollapse(int launchResult) {
+        return mState != StatusBarState.SHADE
+                || (launchResult != ActivityManager.START_TASK_TO_FRONT
+                        && launchResult != ActivityManager.START_SUCCESS);
+    }
+
+    public void onExpandAnimationFinished() {
+        if (!isPresenterFullyCollapsed()) {
+            instantCollapseNotificationPanel();
+            visibilityChanged(false);
+        }
+    }
+
+    public void collapsePanel(boolean animate) {
+        if (animate) {
+            collapsePanel();
+        } else if (!isPresenterFullyCollapsed()) {
+            instantCollapseNotificationPanel();
+            visibilityChanged(false);
+        }
+    }
+
+    private boolean collapsePanel() {
+        if (!mNotificationPanel.isFullyCollapsed()) {
+            // close the shade if it was open
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+                    true /* delayed */);
+            visibilityChanged(false);
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     private void removeNotification(StatusBarNotification notification) {
         // We have to post it to the UI thread for synchronization
         mHandler.post(() -> {
@@ -5058,15 +5120,20 @@
     }
 
     @Override
-    public void startNotificationGutsIntent(final Intent intent, final int appUid) {
+    public void startNotificationGutsIntent(final Intent intent, final int appUid,
+            ExpandableNotificationRow row) {
         dismissKeyguardThenExecute(() -> {
             AsyncTask.execute(() -> {
-                TaskStackBuilder.create(mContext)
+                int launchResult = TaskStackBuilder.create(mContext)
                         .addNextIntentWithParentStack(intent)
-                        .startActivities(getActivityOptions(),
+                        .startActivities(getActivityOptions(row),
                                 new UserHandle(UserHandle.getUserId(appUid)));
+                if (shouldCollapse(launchResult)) {
+                    // Putting it back on the main thread, since we're touching views
+                    mStatusBarWindow.post(() -> animateCollapsePanels(
+                            CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */));
+                }
             });
-            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
             return true;
         }, false /* afterKeyguardGone */);
     }
@@ -5192,7 +5259,8 @@
                 } catch (RemoteException e) {
                 }
                 try {
-                    intent.send(null, 0, null, null, null, null, getActivityOptions());
+                    intent.send(null, 0, null, null, null, null, getActivityOptions(
+                            null /* sourceNotification */));
                 } catch (PendingIntent.CanceledException e) {
                     // the stack trace isn't very helpful here.
                     // Just log the exception message.
@@ -5205,16 +5273,7 @@
                 }
             }).start();
 
-            if (!mNotificationPanel.isFullyCollapsed()) {
-                // close the shade if it was open
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
-                        true /* delayed */);
-                visibilityChanged(false);
-
-                return true;
-            } else {
-                return false;
-            }
+            return collapsePanel();
         }, afterKeyguardGone);
     }
 
@@ -5229,10 +5288,15 @@
         return true;
     }
 
-    protected Bundle getActivityOptions() {
+    protected Bundle getActivityOptions(ExpandableNotificationRow sourceNotification) {
+        ActivityOptions options;
+        if (sourceNotification != null) {
+            options = mActivityLaunchAnimator.getLaunchAnimation(sourceNotification);
+        } else {
+            options = ActivityOptions.makeBasic();
+        }
         // Anything launched from the notification shade should always go into the secondary
         // split-screen windowing mode.
-        final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
         return options.toBundle();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 4accd86..f7d0967 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -88,6 +88,7 @@
     private ViewTreeObserver.OnPreDrawListener mFloatingToolbarPreDrawListener;
     private boolean mTouchCancelled;
     private boolean mTouchActive;
+    private boolean mExpandAnimationRunning;
 
     public StatusBarWindowView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -267,7 +268,7 @@
                 || ev.getActionMasked() == MotionEvent.ACTION_CANCEL) {
             setTouchActive(false);
         }
-        if (mTouchCancelled) {
+        if (mTouchCancelled || mExpandAnimationRunning) {
             return false;
         }
         mFalsingManager.onTouchEvent(ev, getWidth(), getHeight());
@@ -388,6 +389,10 @@
         }
     }
 
+    public void setExpandAnimationRunning(boolean expandAnimationRunning) {
+        mExpandAnimationRunning = expandAnimationRunning;
+    }
+
     public class LayoutParams extends FrameLayout.LayoutParams {
 
         public boolean ignoreRightInset;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index b63c1da..179c0d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -169,6 +169,10 @@
         }
     }
 
+    public CharSequence getText() {
+        return mEditText.getText();
+    }
+
     public static RemoteInputView inflate(Context context, ViewGroup root,
             NotificationData.Entry entry,
             RemoteInputController controller) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
index ebf4cda..424858a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/AmbientState.java
@@ -68,6 +68,8 @@
     private boolean mUnlockHintRunning;
     private boolean mQsCustomizerShowing;
     private int mIntrinsicPadding;
+    private int mExpandAnimationTopChange;
+    private ExpandableNotificationRow mExpandingNotification;
 
     public AmbientState(Context context) {
         reload(context);
@@ -77,9 +79,25 @@
      * Reload the dimens e.g. if the density changed.
      */
     public void reload(Context context) {
-        mZDistanceBetweenElements = Math.max(1, context.getResources()
+        mZDistanceBetweenElements = getZDistanceBetweenElements(context);
+        mBaseZHeight = getBaseHeight(mZDistanceBetweenElements);
+    }
+
+    private static int getZDistanceBetweenElements(Context context) {
+        return Math.max(1, context.getResources()
                 .getDimensionPixelSize(R.dimen.z_distance_between_notifications));
-        mBaseZHeight = 4 * mZDistanceBetweenElements;
+    }
+
+    private static int getBaseHeight(int zdistanceBetweenElements) {
+        return 4 * zdistanceBetweenElements;
+    }
+
+    /**
+     * @return the launch height for notifications that are launched
+     */
+    public static int getNotificationLaunchHeight(Context context) {
+        int zDistance = getZDistanceBetweenElements(context);
+        return getBaseHeight(zDistance) * 2;
     }
 
     /**
@@ -202,7 +220,8 @@
     }
 
     public int getInnerHeight() {
-        return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding, mLayoutMinHeight);
+        return Math.max(Math.min(mLayoutHeight, mMaxLayoutHeight) - mTopPadding
+                - mExpandAnimationTopChange, mLayoutMinHeight);
     }
 
     public boolean isShadeExpanded() {
@@ -380,4 +399,20 @@
     public boolean isDozingAndNotPulsing(ExpandableNotificationRow row) {
         return isDark() && !isPulsing(row.getEntry());
     }
+
+    public void setExpandAnimationTopChange(int expandAnimationTopChange) {
+        mExpandAnimationTopChange = expandAnimationTopChange;
+    }
+
+    public void setExpandingNotification(ExpandableNotificationRow row) {
+        mExpandingNotification = row;
+    }
+
+    public ExpandableNotificationRow getExpandingNotification() {
+        return mExpandingNotification;
+    }
+
+    public int getExpandAnimationTopChange() {
+        return mExpandAnimationTopChange;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
index 0650e23..3bf7d89 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/ExpandableViewState.java
@@ -467,4 +467,21 @@
             return getChildTag(view, TAG_END_HEIGHT);
         }
     }
+
+    @Override
+    public void cancelAnimations(View view) {
+        super.cancelAnimations(view);
+        Animator animator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
+        if (animator != null) {
+            animator.cancel();
+        }
+        animator = getChildTag(view, TAG_ANIMATOR_SHADOW_ALPHA);
+        if (animator != null) {
+            animator.cancel();
+        }
+        animator = getChildTag(view, TAG_ANIMATOR_TOP_INSET);
+        if (animator != null) {
+            animator.cancel();
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index af3d64b..ad8a0eb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.stack;
 
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -728,7 +730,7 @@
     }
 
     private void updateClippingToTopRoundedCorner() {
-        Float clipStart = (float) mTopPadding;
+        Float clipStart = (float) mTopPadding + mAmbientState.getExpandAnimationTopChange();
         Float clipEnd = clipStart + mCornerRadius;
         boolean first = true;
         for (int i = 0; i < getChildCount(); i++) {
@@ -1019,7 +1021,9 @@
                 mHeadsUpManager.addSwipedOutNotification(row.getStatusBarNotification().getKey());
             }
         }
-        performDismiss(v, mGroupManager, false /* fromAccessibility */);
+        if (v instanceof ExpandableNotificationRow) {
+            ((ExpandableNotificationRow) v).performDismiss(false /* fromAccessibility */);
+        }
 
         mFalsingManager.onNotificationDismissed();
         if (mFalsingManager.shouldEnforceBouncer()) {
@@ -1028,26 +1032,6 @@
         }
     }
 
-    public static void performDismiss(View v, NotificationGroupManager groupManager,
-            boolean fromAccessibility) {
-        if (!(v instanceof ExpandableNotificationRow)) {
-            return;
-        }
-        ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-        if (groupManager.isOnlyChildInGroup(row.getStatusBarNotification())) {
-            ExpandableNotificationRow groupSummary =
-                    groupManager.getLogicalGroupSummary(row.getStatusBarNotification());
-            if (groupSummary.isClearable()) {
-                performDismiss(groupSummary, groupManager, fromAccessibility);
-            }
-        }
-        row.setDismissed(true, fromAccessibility);
-        if (row.isClearable()) {
-            row.performDismiss();
-        }
-        if (DEBUG) Log.v(TAG, "onChildDismissed: " + v);
-    }
-
     @Override
     public void onChildSnappedBack(View animView, float targetLeft) {
         mAmbientState.onDragFinished(animView);
@@ -3006,6 +2990,17 @@
                 && (mIsExpanded || isPinnedHeadsUp(child)), child);
     }
 
+    @Override
+    public void setExpandingNotification(ExpandableNotificationRow row) {
+        mAmbientState.setExpandingNotification(row);
+        requestChildrenUpdate();
+    }
+
+    @Override
+    public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+        mAmbientState.setExpandAnimationTopChange(params == null ? 0 : params.getTopChange());
+        requestChildrenUpdate();
+    }
 
     private void updateAnimationState(boolean running, View child) {
         if (child instanceof ExpandableNotificationRow) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
index 2ce6df2..d68a7b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/StackScrollAlgorithm.java
@@ -130,7 +130,8 @@
     private void updateClipping(StackScrollState resultState,
             StackScrollAlgorithmState algorithmState, AmbientState ambientState) {
         float drawStart = !ambientState.isOnKeyguard() ? ambientState.getTopPadding()
-                + ambientState.getStackTranslation() : 0;
+                + ambientState.getStackTranslation() + ambientState.getExpandAnimationTopChange()
+                : 0;
         float previousNotificationEnd = 0;
         float previousNotificationStart = 0;
         int childCount = algorithmState.visibleChildren.size();
@@ -320,6 +321,10 @@
                 lastView = v;
             }
         }
+        ExpandableNotificationRow expandingNotification = ambientState.getExpandingNotification();
+        state.indexOfExpandingNotification = expandingNotification != null
+                ? state.visibleChildren.indexOf(expandingNotification)
+                : -1;
     }
 
     private float getPaddingForValue(Float increasedPadding) {
@@ -381,6 +386,9 @@
 
         childViewState.location = ExpandableViewState.LOCATION_MAIN_AREA;
         float inset = ambientState.getTopPadding() + ambientState.getStackTranslation();
+        if (i < algorithmState.getIndexOfExpandingNotification()) {
+            inset += ambientState.getExpandAnimationTopChange();
+        }
         if (child.mustStayOnScreen() && childViewState.yTranslation >= 0) {
             // Even if we're not scrolled away we're in view and we're also not in the
             // shelf. We can relax the constraints and let us scroll off the top!
@@ -394,7 +402,7 @@
             childViewState.yTranslation = ambientState.getInnerHeight() - childHeight
                     + ambientState.getStackTranslation() * 0.25f;
         } else {
-            clampPositionToShelf(childViewState, ambientState);
+            clampPositionToShelf(child, childViewState, ambientState);
         }
 
         currentYPosition = childViewState.yTranslation + childHeight + paddingAfterChild;
@@ -492,10 +500,12 @@
      * Clamp the height of the child down such that its end is at most on the beginning of
      * the shelf.
      *
+     * @param child
      * @param childViewState the view state of the child
      * @param ambientState the ambient state
      */
-    private void clampPositionToShelf(ExpandableViewState childViewState,
+    private void clampPositionToShelf(ExpandableView child,
+            ExpandableViewState childViewState,
             AmbientState ambientState) {
         if (ambientState.getShelf() == null) {
             return;
@@ -505,7 +515,7 @@
                 - ambientState.getShelf().getIntrinsicHeight();
         childViewState.yTranslation = Math.min(childViewState.yTranslation, shelfStart);
         if (childViewState.yTranslation >= shelfStart) {
-            childViewState.hidden = true;
+            childViewState.hidden = !child.isExpandAnimationRunning();
             childViewState.inShelf = true;
             childViewState.headsUpIsVisible = false;
         }
@@ -602,6 +612,7 @@
          * The padding after each child measured in pixels.
          */
         public final HashMap<ExpandableView, Float> paddingMap = new HashMap<>();
+        private int indexOfExpandingNotification;
 
         public int getPaddingAfterChild(ExpandableView child) {
             Float padding = paddingMap.get(child);
@@ -611,6 +622,10 @@
             }
             return (int) padding.floatValue();
         }
+
+        public int getIndexOfExpandingNotification() {
+            return indexOfExpandingNotification;
+        }
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index e3c8503..5c888ac 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -514,5 +514,8 @@
 
         @Override
         public void onAccessibilityModeChanged(Boolean showA11yStream) {}
+
+        @Override
+        public void onConnectedDeviceChanged(String deviceName) {}
     };
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 4464f75..2e23920 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -26,6 +26,8 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.ContentObserver;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.IVolumeController;
@@ -105,6 +107,8 @@
     private boolean mShowA11yStream;
     private boolean mShowVolumeDialog;
     private boolean mShowSafetyWarning;
+    private DeviceCallback mDeviceCallback = new DeviceCallback();
+    private AudioDeviceInfo mConnectedDevice;
 
     private boolean mDestroyed;
     private VolumePolicy mVolumePolicy;
@@ -180,6 +184,7 @@
         } catch (SecurityException e) {
             Log.w(TAG, "No access to media sessions", e);
         }
+        mAudio.registerAudioDeviceCallback(mDeviceCallback, mWorker);
     }
 
     public void setVolumePolicy(VolumePolicy policy) {
@@ -205,6 +210,7 @@
         mMediaSessions.destroy();
         mObserver.destroy();
         mReceiver.destroy();
+        mAudio.unregisterAudioDeviceCallback(mDeviceCallback);
         mWorkerThread.quitSafely();
     }
 
@@ -664,6 +670,7 @@
                 case USER_ACTIVITY: onUserActivityW(); break;
                 case SHOW_SAFETY_WARNING: onShowSafetyWarningW(msg.arg1); break;
                 case ACCESSIBILITY_MODE_CHANGED: onAccessibilityModeChanged((Boolean) msg.obj);
+
             }
         }
     }
@@ -803,6 +810,18 @@
                 });
             }
         }
+
+        @Override
+        public void onConnectedDeviceChanged(String deviceName) {
+            for (final Map.Entry<Callbacks, Handler> entry : mCallbackMap.entrySet()) {
+                entry.getValue().post(new Runnable() {
+                    @Override
+                    public void run() {
+                        entry.getKey().onConnectedDeviceChanged(deviceName);
+                    }
+                });
+            }
+        }
     }
 
 
@@ -1005,6 +1024,34 @@
         }
     }
 
+    protected final class DeviceCallback extends AudioDeviceCallback {
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            for (AudioDeviceInfo info : addedDevices) {
+                if (info.isSink()
+                        && (info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP
+                        || info.getType() == AudioDeviceInfo.TYPE_BLUETOOTH_SCO)) {
+                    mConnectedDevice = info;
+                    mCallbacks.onConnectedDeviceChanged(info.getProductName().toString());
+                }
+            }
+        }
+
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            if (mConnectedDevice == null) {
+                mCallbacks.onConnectedDeviceChanged(null);
+                return;
+            }
+            for (AudioDeviceInfo info : removedDevices) {
+                if (info.isSink() == mConnectedDevice.isSink()
+                        && Objects.equals(info.getProductName(), mConnectedDevice.getProductName())
+                        && info.getType() == mConnectedDevice.getType()) {
+                    mConnectedDevice = null;
+                    mCallbacks.onConnectedDeviceChanged(null);
+                }
+            }
+        }
+    }
+
     public interface UserActivityListener {
         void onUserActivity();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 385438c..56b7201 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -48,6 +48,7 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.support.v7.media.MediaRouter;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
@@ -73,6 +74,7 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.VolumeDialog;
 import com.android.systemui.plugins.VolumeDialogController;
 import com.android.systemui.plugins.VolumeDialogController.State;
@@ -332,8 +334,11 @@
         row.slider.setOnSeekBarChangeListener(new VolumeSeekBarChangeListener(row));
         row.anim = null;
 
-        ImageButton outputChooser = row.view.findViewById(R.id.output_chooser);
-        outputChooser.setOnClickListener(mClickOutputChooser);
+        row.outputChooser = row.view.findViewById(R.id.output_chooser);
+        row.outputChooser.setOnClickListener(mClickOutputChooser);
+        row.outputChooser.findViewById(R.id.output_chooser_button)
+                .setOnClickListener(mClickOutputChooser);
+        row.connectedDevice = row.view.findViewById(R.id.volume_row_connected_device);
 
         // forward events above the slider into the slider
         row.view.setOnTouchListener(new OnTouchListener() {
@@ -422,7 +427,7 @@
             Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             dismissH(DISMISS_REASON_SETTINGS_CLICKED);
-            mContext.startActivity(intent);
+            Dependency.get(ActivityStarter.class).startActivity(intent, true /* dismissShade */);
             return true;
         });
         updateRingerH();
@@ -546,6 +551,13 @@
         }
     }
 
+    protected void updateConnectedDeviceH(String deviceName) {
+        for (final VolumeRow row : mRows) {
+            row.connectedDevice.setText(deviceName);
+            Util.setVisOrGone(row.connectedDevice, !TextUtils.isEmpty(deviceName));
+        }
+    }
+
     protected void updateRingerH() {
         if (mState != null) {
             final StreamState ss = mState.states.get(AudioManager.STREAM_RING);
@@ -957,6 +969,11 @@
             }
 
         }
+
+        @Override
+        public void onConnectedDeviceChanged(String deviceName) {
+            updateConnectedDeviceH(deviceName);
+        }
     };
 
     private final class H extends Handler {
@@ -1155,5 +1172,7 @@
         private ObjectAnimator anim;  // slider progress animation for non-touch-related updates
         private int animTargetProgress;
         private int lastAudibleLevel = 1;
+        private View outputChooser;
+        private TextView connectedDevice;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
index 53a7994..5c83d99 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -18,9 +18,11 @@
 
 import android.content.Context;
 import android.util.ArrayMap;
+import android.util.ArraySet;
 
 public class TestableDependency extends Dependency {
     private final ArrayMap<Object, Object> mObjs = new ArrayMap<>();
+    private final ArraySet<Object> mInstantiatedObjects = new ArraySet<>();
 
     public TestableDependency(Context context) {
         mContext = context;
@@ -47,6 +49,11 @@
     @Override
     protected <T> T createDependency(Object key) {
         if (mObjs.containsKey(key)) return (T) mObjs.get(key);
+        mInstantiatedObjects.add(key);
         return super.createDependency(key);
     }
+
+    public <T> boolean hasInstantiatedDependency(Class<T> key) {
+        return mObjs.containsKey(key) || mInstantiatedObjects.contains(key);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index fdb7f8d..0a51e5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -40,6 +40,7 @@
 import com.android.systemui.power.PowerUI.WarningsUI;
 import com.android.systemui.statusbar.phone.StatusBar;
 
+import java.time.Duration;
 import java.util.concurrent.TimeUnit;
 import org.junit.Before;
 import org.junit.Test;
@@ -53,18 +54,19 @@
     private static final boolean UNPLUGGED = false;
     private static final boolean POWER_SAVER_OFF = false;
     private static final int ABOVE_WARNING_BUCKET = 1;
+    private static final long ONE_HOUR_MILLIS = Duration.ofHours(1).toMillis();
     public static final int BELOW_WARNING_BUCKET = -1;
     public static final long BELOW_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(2);
     public static final long ABOVE_HYBRID_THRESHOLD = TimeUnit.HOURS.toMillis(4);
     private HardwarePropertiesManager mHardProps;
     private WarningsUI mMockWarnings;
     private PowerUI mPowerUI;
-    private EnhancedEstimates mEnhacedEstimates;
+    private EnhancedEstimates mEnhancedEstimates;
 
     @Before
     public void setup() {
         mMockWarnings = mDependency.injectMockDependency(WarningsUI.class);
-        mEnhacedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class);
+        mEnhancedEstimates = mDependency.injectMockDependency(EnhancedEstimates.class);
         mHardProps = mock(HardwarePropertiesManager.class);
 
         mContext.putComponent(StatusBar.class, mock(StatusBar.class));
@@ -142,8 +144,44 @@
     }
 
     @Test
+    public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsNoShow() {
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold())
+                .thenReturn(Duration.ofHours(1).toMillis());
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+        mPowerUI.start();
+
+        // unplugged device that would not show the non-hybrid notification but would show the
+        // hybrid but the threshold has been overriden to be too low
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, Long.MAX_VALUE, BELOW_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertFalse(shouldShow);
+    }
+
+    @Test
+    public void testShouldShowLowBatteryWarning_showHybridOnly_overrideThresholdHigh_returnsShow() {
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold())
+                .thenReturn(Duration.ofHours(5).toMillis());
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+        mPowerUI.start();
+
+        // unplugged device that would not show the non-hybrid notification but would show the
+        // hybrid since the threshold has been overriden to be much higher
+        boolean shouldShow =
+                mPowerUI.shouldShowLowBatteryWarning(UNPLUGGED, UNPLUGGED, ABOVE_WARNING_BUCKET,
+                        ABOVE_WARNING_BUCKET, Long.MAX_VALUE, ABOVE_HYBRID_THRESHOLD,
+                        POWER_SAVER_OFF, BatteryManager.BATTERY_HEALTH_GOOD);
+        assertTrue(shouldShow);
+    }
+
+    @Test
     public void testShouldShowLowBatteryWarning_showHybridOnly_returnsShow() {
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
         mPowerUI.start();
 
         // unplugged device that would not show the non-hybrid notification but would show the
@@ -157,7 +195,9 @@
 
     @Test
     public void testShouldShowLowBatteryWarning_showHybrid_showStandard_returnsShow() {
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
         mPowerUI.start();
 
         // unplugged device that would show the non-hybrid notification and the hybrid
@@ -170,7 +210,9 @@
 
     @Test
     public void testShouldShowLowBatteryWarning_showStandardOnly_returnsShow() {
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
         mPowerUI.start();
 
         // unplugged device that would show the non-hybrid but not the hybrid
@@ -183,7 +225,9 @@
 
     @Test
     public void testShouldShowLowBatteryWarning_deviceHighBattery_returnsNoShow() {
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
         mPowerUI.start();
 
         // unplugged device that would show the neither due to battery level being good
@@ -196,7 +240,9 @@
 
     @Test
     public void testShouldShowLowBatteryWarning_devicePlugged_returnsNoShow() {
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
         mPowerUI.start();
 
         // plugged device that would show the neither due to being plugged
@@ -209,7 +255,9 @@
 
     @Test
     public void testShouldShowLowBatteryWarning_deviceBatteryStatusUnkown_returnsNoShow() {
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
         mPowerUI.start();
 
         // Unknown battery status device that would show the neither due
@@ -222,7 +270,9 @@
 
     @Test
     public void testShouldShowLowBatteryWarning_batterySaverEnabled_returnsNoShow() {
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
         mPowerUI.start();
 
         // BatterySaverEnabled device that would show the neither due to battery saver
@@ -236,7 +286,10 @@
     @Test
     public void testShouldDismissLowBatteryWarning_dismissWhenPowerSaverEnabled() {
         mPowerUI.start();
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+
         // device that gets power saver turned on should dismiss
         boolean shouldDismiss =
                 mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
@@ -247,7 +300,9 @@
     @Test
     public void testShouldDismissLowBatteryWarning_dismissWhenPlugged() {
         mPowerUI.start();
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
 
         // device that gets plugged in should dismiss
         boolean shouldDismiss =
@@ -259,7 +314,10 @@
     @Test
     public void testShouldDismissLowBatteryWarning_dismissHybridSignal_showStandardSignal_shouldShow() {
         mPowerUI.start();
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
+
         // would dismiss hybrid but not non-hybrid should not dismiss
         boolean shouldDismiss =
                 mPowerUI.shouldDismissLowBatteryWarning(UNPLUGGED, BELOW_WARNING_BUCKET,
@@ -270,7 +328,9 @@
     @Test
     public void testShouldDismissLowBatteryWarning_showHybridSignal_dismissStandardSignal_shouldShow() {
         mPowerUI.start();
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
 
         // would dismiss non-hybrid but not hybrid should not dismiss
         boolean shouldDismiss =
@@ -282,7 +342,9 @@
     @Test
     public void testShouldDismissLowBatteryWarning_showBothSignal_shouldShow() {
         mPowerUI.start();
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
 
         // should not dismiss when both would not dismiss
         boolean shouldDismiss =
@@ -294,7 +356,9 @@
     @Test
     public void testShouldDismissLowBatteryWarning_dismissBothSignal_shouldDismiss() {
         mPowerUI.start();
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(true);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
 
         //should dismiss if both would dismiss
         boolean shouldDismiss =
@@ -306,7 +370,9 @@
     @Test
     public void testShouldDismissLowBatteryWarning_dismissStandardSignal_hybridDisabled_shouldDismiss() {
         mPowerUI.start();
-        when(mEnhacedEstimates.isHybridNotificationEnabled()).thenReturn(false);
+        when(mEnhancedEstimates.isHybridNotificationEnabled()).thenReturn(false);
+        when(mEnhancedEstimates.getLowWarningThreshold()).thenReturn(PowerUI.THREE_HOURS_IN_MILLIS);
+        when(mEnhancedEstimates.getSevereWarningThreshold()).thenReturn(ONE_HOUR_MILLIS);
 
         // would dismiss non-hybrid with hybrid disabled should dismiss
         boolean shouldDismiss =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
new file mode 100644
index 0000000..5f763a4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NonPhoneDependencyTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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 com.android.systemui.statusbar;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.Mockito.when;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.filters.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.systemui.Dependency;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.statusbar.phone.StatusBarWindowManager;
+import com.android.systemui.statusbar.policy.HeadsUpManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Verifies that particular sets of dependencies don't have dependencies on others. For example,
+ * code managing notifications shouldn't directly depend on StatusBar, since there are platforms
+ * which want to manage notifications, but don't use StatusBar.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NonPhoneDependencyTest extends SysuiTestCase {
+    @Mock private NotificationPresenter mPresenter;
+    @Mock private NotificationListContainer mListContainer;
+    @Mock private NotificationEntryManager.Callback mEntryManagerCallback;
+    @Mock private HeadsUpManager mHeadsUpManager;
+    @Mock private RemoteInputController.Delegate mDelegate;
+    @Mock private NotificationInfo.CheckSaveListener mCheckSaveListener;
+    @Mock private NotificationGutsManager.OnSettingsClickListener mOnClickListener;
+    @Mock private NotificationRemoteInputManager.Callback mRemoteInputManagerCallback;
+
+    private Handler mHandler;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mHandler = new Handler(Looper.getMainLooper());
+        when(mPresenter.getHandler()).thenReturn(mHandler);
+    }
+
+    @Test
+    public void testNotificationManagementCodeHasNoDependencyOnStatusBarWindowManager() {
+        NotificationEntryManager entryManager = Dependency.get(NotificationEntryManager.class);
+        NotificationGutsManager gutsManager = Dependency.get(NotificationGutsManager.class);
+        NotificationListener notificationListener = Dependency.get(NotificationListener.class);
+        NotificationLogger notificationLogger = Dependency.get(NotificationLogger.class);
+        NotificationMediaManager mediaManager = Dependency.get(NotificationMediaManager.class);
+        NotificationRemoteInputManager remoteInputManager =
+                Dependency.get(NotificationRemoteInputManager.class);
+        NotificationLockscreenUserManager lockscreenUserManager =
+                Dependency.get(NotificationLockscreenUserManager.class);
+        NotificationViewHierarchyManager viewHierarchyManager =
+                Dependency.get(NotificationViewHierarchyManager.class);
+
+        when(mPresenter.getNotificationLockscreenUserManager()).thenReturn(lockscreenUserManager);
+        when(mPresenter.getGroupManager()).thenReturn(
+                Dependency.get(NotificationGroupManager.class));
+
+        entryManager.setUpWithPresenter(mPresenter, mListContainer, mEntryManagerCallback,
+                mHeadsUpManager);
+        gutsManager.setUpWithPresenter(mPresenter, entryManager, mListContainer,
+                mCheckSaveListener, mOnClickListener);
+        notificationListener.setUpWithPresenter(mPresenter, entryManager);
+        notificationLogger.setUpWithEntryManager(entryManager, mListContainer);
+        mediaManager.setUpWithPresenter(mPresenter, entryManager);
+        remoteInputManager.setUpWithPresenter(mPresenter, entryManager, mRemoteInputManagerCallback,
+                mDelegate);
+        lockscreenUserManager.setUpWithPresenter(mPresenter, entryManager);
+        viewHierarchyManager.setUpWithPresenter(mPresenter, entryManager, mListContainer);
+
+        assertFalse(mDependency.hasInstantiatedDependency(StatusBarWindowManager.class));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
index d77bf69..8e8b3e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java
@@ -281,6 +281,16 @@
     }
 
     @Test
+    public void testbindNotification_BlockingHelper() throws Exception {
+        mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
+                TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null,
+                null, null, true);
+        final TextView view = mNotificationInfo.findViewById(R.id.block_prompt);
+        assertEquals(View.VISIBLE, view.getVisibility());
+        assertEquals(mContext.getString(R.string.inline_blocking_helper), view.getText());
+    }
+
+    @Test
     public void testbindNotification_UnblockableTextVisibleWhenAppUnblockable() throws Exception {
         mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager,
                 TEST_PACKAGE_NAME, mNotificationChannel, 1, mSbn, null, null,
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index bfec88c..699231f 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -4872,17 +4872,17 @@
 
     // An autofill service asked to disable autofill for a given application.
     // Package: Package of app that is being disabled for autofill
-    // Counter: duration (in ms) that autofill will be disabled
     // OS: P
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Tag FIELD_AUTOFILL_DURATION: duration (in ms) that autofill will be disabled
     AUTOFILL_SERVICE_DISABLED_APP = 1231;
 
     // An autofill service asked to disable autofill for a given activity.
     // Package: Package of app whose activity is being disabled for autofill
-    // Counter: duration (in ms) that autofill will be disabled
     // OS: P
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Tag FIELD_CLASS_NAME: Class name of the activity that is being disabled for autofill
+    // Tag FIELD_AUTOFILL_DURATION: duration (in ms) that autofill will be disabled
     AUTOFILL_SERVICE_DISABLED_ACTIVITY = 1232;
 
     // ACTION: Stop an app and turn on background check
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 7c6019e..db70184 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -196,6 +196,10 @@
     // Inform the user that unexpectedly rapid network usage is happening
     NOTE_NET_RAPID = 45;
 
+    // Notify the user that carrier Wi-Fi networks are available.
+    // Package: android
+    NOTE_CARRIER_NETWORK_AVAILABLE = 46;
+
     // ADD_NEW_IDS_ABOVE_THIS_LINE
     // Legacy IDs with arbitrary values appear below
     // Legacy IDs existed as stable non-conflicting constants prior to the O release
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/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 07b0b77..eb031f3 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -1066,7 +1066,7 @@
             int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
             mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP,
                     packageName, getServicePackageName())
-                    .setCounterValue(intDuration));
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration));
         }
     }
 
@@ -1090,7 +1090,7 @@
             mMetricsLogger.write(new LogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY)
                     .setComponentName(componentName)
                     .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName())
-                    .setCounterValue(intDuration));
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration));
         }
     }
 
diff --git a/services/backup/java/com/android/server/backup/DataChangedJournal.java b/services/backup/java/com/android/server/backup/DataChangedJournal.java
index 9360c85..c2d3829 100644
--- a/services/backup/java/com/android/server/backup/DataChangedJournal.java
+++ b/services/backup/java/com/android/server/backup/DataChangedJournal.java
@@ -33,7 +33,7 @@
  * <p>This information is persisted to the filesystem so that it is not lost in the event of a
  * reboot.
  */
-public final class DataChangedJournal {
+public class DataChangedJournal {
     private static final String FILE_NAME_PREFIX = "journal";
 
     /**
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index b38b25a..42785be 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -153,8 +153,9 @@
                     OP_TYPE_BACKUP_WAIT);
 
             // Start backup and wait for BackupManagerService to get callback for success or timeout
-            agent.doBackup(mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
-                    mBackupManagerService.getBackupManagerBinder());
+            agent.doBackup(
+                    mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
+                    mBackupManagerService.getBackupManagerBinder(), /*transportFlags=*/ 0);
             if (!mBackupManagerService.waitUntilOperationComplete(token)) {
                 Slog.e(TAG, "Key-value backup failed on package " + packageName);
                 return false;
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 465bb09..dc20d31 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -65,6 +65,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Environment;
+import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Message;
@@ -387,7 +388,7 @@
         mWakelock = wakelock;
     }
 
-    public BackupHandler getBackupHandler() {
+    public Handler getBackupHandler() {
         return mBackupHandler;
     }
 
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index 3bf77e8..d460f4d 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -74,6 +74,7 @@
     PackageInfo mPkg;
     private final long mQuota;
     private final int mOpToken;
+    private final int mTransportFlags;
 
     class FullBackupRunner implements Runnable {
 
@@ -100,7 +101,8 @@
         @Override
         public void run() {
             try {
-                FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
+                FullBackupDataOutput output = new FullBackupDataOutput(
+                        mPipe, -1, mTransportFlags);
 
                 if (mWriteManifest) {
                     final boolean writeWidgetData = mWidgetData != null;
@@ -147,7 +149,7 @@
                                 mTimeoutMonitor /* in parent class */,
                                 OP_TYPE_BACKUP_WAIT);
                 mAgent.doFullBackup(mPipe, mQuota, mToken,
-                        backupManagerService.getBackupManagerBinder());
+                        backupManagerService.getBackupManagerBinder(), mTransportFlags);
             } catch (IOException e) {
                 Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
             } catch (RemoteException e) {
@@ -164,7 +166,8 @@
     public FullBackupEngine(RefactoredBackupManagerService backupManagerService,
             OutputStream output,
             FullBackupPreflight preflightHook, PackageInfo pkg,
-            boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken) {
+            boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken,
+            int transportFlags) {
         this.backupManagerService = backupManagerService;
         mOutput = output;
         mPreflightHook = preflightHook;
@@ -176,6 +179,7 @@
         mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
         mQuota = quota;
         mOpToken = opToken;
+        mTransportFlags = transportFlags;
     }
 
     public int preflightCheck() throws RemoteException {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index f0b3e4a..19e601b 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -410,7 +410,8 @@
                                 SHARED_BACKUP_AGENT_PACKAGE);
 
                 mBackupEngine = new FullBackupEngine(backupManagerService, out,
-                        null, pkg, mIncludeApks, this, Long.MAX_VALUE, mCurrentOpToken);
+                        null, pkg, mIncludeApks, this, Long.MAX_VALUE,
+                        mCurrentOpToken, /*transportFlags=*/ 0);
                 sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
 
                 // Don't need to check preflight result as there is no preflight hook.
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index d5b3d98..d04be12 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -378,7 +378,8 @@
                         enginePipes = ParcelFileDescriptor.createPipe();
                         mBackupRunner =
                                 new SinglePackageBackupRunner(enginePipes[1], currentPackage,
-                                        mTransportClient, quota, mBackupRunnerOpToken);
+                                        mTransportClient, quota, mBackupRunnerOpToken,
+                                        transport.getTransportFlags());
                         // The runner dup'd the pipe half, so we close it here
                         enginePipes[1].close();
                         enginePipes[1] = null;
@@ -681,12 +682,17 @@
         final TransportClient mTransportClient;
         final long mQuota;
         private final int mCurrentOpToken;
+        private final int mTransportFlags;
 
         SinglePackageBackupPreflight(
-                TransportClient transportClient, long quota, int currentOpToken) {
+                TransportClient transportClient,
+                long quota,
+                int currentOpToken,
+                int transportFlags) {
             mTransportClient = transportClient;
             mQuota = quota;
             mCurrentOpToken = currentOpToken;
+            mTransportFlags = transportFlags;
         }
 
         @Override
@@ -700,7 +706,7 @@
                     Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
                 }
                 agent.doMeasureFullBackup(mQuota, mCurrentOpToken,
-                        backupManagerService.getBackupManagerBinder());
+                        backupManagerService.getBackupManagerBinder(), mTransportFlags);
 
                 // Now wait to get our result back.  If this backstop timeout is reached without
                 // the latch being thrown, flow will continue as though a result or "normal"
@@ -785,20 +791,23 @@
         private volatile int mBackupResult;
         private final long mQuota;
         private volatile boolean mIsCancelled;
+        private final int mTransportFlags;
 
         SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
-                TransportClient transportClient, long quota, int currentOpToken)
+                TransportClient transportClient, long quota, int currentOpToken, int transportFlags)
                 throws IOException {
             mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
             mTarget = target;
             mCurrentOpToken = currentOpToken;
             mEphemeralToken = backupManagerService.generateRandomIntegerToken();
-            mPreflight = new SinglePackageBackupPreflight(transportClient, quota, mEphemeralToken);
+            mPreflight = new SinglePackageBackupPreflight(
+                    transportClient, quota, mEphemeralToken, transportFlags);
             mPreflightLatch = new CountDownLatch(1);
             mBackupLatch = new CountDownLatch(1);
             mPreflightResult = BackupTransport.AGENT_ERROR;
             mBackupResult = BackupTransport.AGENT_ERROR;
             mQuota = quota;
+            mTransportFlags = transportFlags;
             registerTask();
         }
 
@@ -819,7 +828,7 @@
         public void run() {
             FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
             mEngine = new FullBackupEngine(backupManagerService, out, mPreflight, mTarget, false,
-                    this, mQuota, mCurrentOpToken);
+                    this, mQuota, mCurrentOpToken, mTransportFlags);
             try {
                 try {
                     if (!mIsCancelled) {
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index 99ffa12..bacb357 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -714,8 +714,9 @@
                     mEphemeralOpToken, TIMEOUT_BACKUP_INTERVAL, this, OP_TYPE_BACKUP_WAIT);
             backupManagerService.addBackupTrace("calling agent doBackup()");
 
-            agent.doBackup(mSavedState, mBackupData, mNewState, quota, mEphemeralOpToken,
-                    backupManagerService.getBackupManagerBinder());
+            agent.doBackup(
+                    mSavedState, mBackupData, mNewState, quota, mEphemeralOpToken,
+                    backupManagerService.getBackupManagerBinder(), transport.getTransportFlags());
         } catch (Exception e) {
             Slog.e(TAG, "Error invoking for backup on " + packageName + ". " + e);
             backupManagerService.addBackupTrace("exception: " + e);
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index 7ae5b43..82106ec 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -30,6 +30,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.Message;
 import android.os.PowerManager;
 import android.util.Slog;
@@ -374,7 +375,7 @@
         }
 
         // Stop the session timeout until we finalize the restore
-        BackupHandler backupHandler = mBackupManagerService.getBackupHandler();
+        Handler backupHandler = mBackupManagerService.getBackupHandler();
         backupHandler.removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
 
         PowerManager.WakeLock wakelock = mBackupManagerService.getWakelock();
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java
index 7b2e3df..1a2ca92 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -33,7 +33,7 @@
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.EventLog;
-import android.util.Log;
+import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -41,9 +41,13 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.EventLogTags;
 import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.TransportUtils.Priority;
+
+import dalvik.system.CloseGuard;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
@@ -70,17 +74,20 @@
  * @see TransportManager
  */
 public class TransportClient {
-    private static final String TAG = "TransportClient";
+    @VisibleForTesting static final String TAG = "TransportClient";
     private static final int LOG_BUFFER_SIZE = 5;
 
     private final Context mContext;
     private final Intent mBindIntent;
+    private final ServiceConnection mConnection;
     private final String mIdentifier;
+    private final String mCreatorLogString;
     private final ComponentName mTransportComponent;
     private final Handler mListenerHandler;
     private final String mPrefixForLog;
     private final Object mStateLock = new Object();
     private final Object mLogBufferLock = new Object();
+    private final CloseGuard mCloseGuard = CloseGuard.get();
 
     @GuardedBy("mLogBufferLock")
     private final List<String> mLogBuffer = new LinkedList<>();
@@ -99,12 +106,14 @@
             Context context,
             Intent bindIntent,
             ComponentName transportComponent,
-            String identifier) {
+            String identifier,
+            String caller) {
         this(
                 context,
                 bindIntent,
                 transportComponent,
                 identifier,
+                caller,
                 new Handler(Looper.getMainLooper()));
     }
 
@@ -114,79 +123,27 @@
             Intent bindIntent,
             ComponentName transportComponent,
             String identifier,
+            String caller,
             Handler listenerHandler) {
         mContext = context;
         mTransportComponent = transportComponent;
         mBindIntent = bindIntent;
         mIdentifier = identifier;
+        mCreatorLogString = caller;
         mListenerHandler = listenerHandler;
+        mConnection = new TransportConnection(context, this);
 
         // For logging
         String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", "");
         mPrefixForLog = classNameForLog + "#" + mIdentifier + ":";
+
+        mCloseGuard.open("markAsDisposed");
     }
 
     public ComponentName getTransportComponent() {
         return mTransportComponent;
     }
 
-    // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one
-    // of these calls, if a binding happen again the new service can be a different instance. Since
-    // transports are stateful, we don't want a new instance responding for an old instance's state.
-    private ServiceConnection mConnection =
-            new ServiceConnection() {
-                @Override
-                public void onServiceConnected(ComponentName componentName, IBinder binder) {
-                    IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
-                    synchronized (mStateLock) {
-                        checkStateIntegrityLocked();
-
-                        if (mState != State.UNUSABLE) {
-                            log(Log.DEBUG, "Transport connected");
-                            setStateLocked(State.CONNECTED, transport);
-                            notifyListenersAndClearLocked(transport);
-                        }
-                    }
-                }
-
-                @Override
-                public void onServiceDisconnected(ComponentName componentName) {
-                    synchronized (mStateLock) {
-                        log(Log.ERROR, "Service disconnected: client UNUSABLE");
-                        setStateLocked(State.UNUSABLE, null);
-                        // After unbindService() no calls back to mConnection
-                        mContext.unbindService(this);
-                    }
-                }
-
-                @Override
-                public void onBindingDied(ComponentName name) {
-                    synchronized (mStateLock) {
-                        checkStateIntegrityLocked();
-
-                        log(Log.ERROR, "Binding died: client UNUSABLE");
-                        // After unbindService() no calls back to mConnection
-                        switch (mState) {
-                            case State.UNUSABLE:
-                                break;
-                            case State.IDLE:
-                                log(Log.ERROR, "Unexpected state transition IDLE => UNUSABLE");
-                                setStateLocked(State.UNUSABLE, null);
-                                break;
-                            case State.BOUND_AND_CONNECTING:
-                                setStateLocked(State.UNUSABLE, null);
-                                mContext.unbindService(this);
-                                notifyListenersAndClearLocked(null);
-                                break;
-                            case State.CONNECTED:
-                                setStateLocked(State.UNUSABLE, null);
-                                mContext.unbindService(this);
-                                break;
-                        }
-                    }
-                }
-            };
-
     /**
      * Attempts to connect to the transport (if needed).
      *
@@ -240,7 +197,7 @@
 
             switch (mState) {
                 case State.UNUSABLE:
-                    log(Log.WARN, caller, "Async connect: UNUSABLE client");
+                    log(Priority.WARN, caller, "Async connect: UNUSABLE client");
                     notifyListener(listener, null, caller);
                     break;
                 case State.IDLE:
@@ -254,22 +211,25 @@
                         // We don't need to set a time-out because we are guaranteed to get a call
                         // back in ServiceConnection, either an onServiceConnected() or
                         // onBindingDied().
-                        log(Log.DEBUG, caller, "Async connect: service bound, connecting");
+                        log(Priority.DEBUG, caller, "Async connect: service bound, connecting");
                         setStateLocked(State.BOUND_AND_CONNECTING, null);
                         mListeners.put(listener, caller);
                     } else {
-                        log(Log.ERROR, "Async connect: bindService returned false");
+                        log(Priority.ERROR, "Async connect: bindService returned false");
                         // mState remains State.IDLE
                         mContext.unbindService(mConnection);
                         notifyListener(listener, null, caller);
                     }
                     break;
                 case State.BOUND_AND_CONNECTING:
-                    log(Log.DEBUG, caller, "Async connect: already connecting, adding listener");
+                    log(
+                            Priority.DEBUG,
+                            caller,
+                            "Async connect: already connecting, adding listener");
                     mListeners.put(listener, caller);
                     break;
                 case State.CONNECTED:
-                    log(Log.DEBUG, caller, "Async connect: reusing transport");
+                    log(Priority.DEBUG, caller, "Async connect: reusing transport");
                     notifyListener(listener, mTransport, caller);
                     break;
             }
@@ -286,7 +246,7 @@
         synchronized (mStateLock) {
             checkStateIntegrityLocked();
 
-            log(Log.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")");
+            log(Priority.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")");
             switch (mState) {
                 case State.UNUSABLE:
                 case State.IDLE:
@@ -305,6 +265,15 @@
         }
     }
 
+    /** Marks this TransportClient as disposed, allowing it to be GC'ed without warnings. */
+    public void markAsDisposed() {
+        synchronized (mStateLock) {
+            Preconditions.checkState(
+                    mState < State.BOUND_AND_CONNECTING, "Can't mark as disposed if still bound");
+            mCloseGuard.close();
+        }
+    }
+
     /**
      * Attempts to connect to the transport (if needed) and returns it.
      *
@@ -335,14 +304,14 @@
 
         IBackupTransport transport = mTransport;
         if (transport != null) {
-            log(Log.INFO, caller, "Sync connect: reusing transport");
+            log(Priority.DEBUG, caller, "Sync connect: reusing transport");
             return transport;
         }
 
         // If it's already UNUSABLE we return straight away, no need to go to main-thread
         synchronized (mStateLock) {
             if (mState == State.UNUSABLE) {
-                log(Log.WARN, caller, "Sync connect: UNUSABLE client");
+                log(Priority.WARN, caller, "Sync connect: UNUSABLE client");
                 return null;
             }
         }
@@ -352,14 +321,14 @@
                 (requestedTransport, transportClient) ->
                         transportFuture.complete(requestedTransport);
 
-        log(Log.DEBUG, caller, "Sync connect: calling async");
+        log(Priority.DEBUG, caller, "Sync connect: calling async");
         connectAsync(requestListener, caller);
 
         try {
             return transportFuture.get();
         } catch (InterruptedException | ExecutionException e) {
             String error = e.getClass().getSimpleName();
-            log(Log.ERROR, caller, error + " while waiting for transport: " + e.getMessage());
+            log(Priority.ERROR, caller, error + " while waiting for transport: " + e.getMessage());
             return null;
         }
     }
@@ -379,7 +348,7 @@
     public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
         IBackupTransport transport = connect(caller);
         if (transport == null) {
-            log(Log.ERROR, caller, "Transport connection failed");
+            log(Priority.ERROR, caller, "Transport connection failed");
             throw new TransportNotAvailableException();
         }
         return transport;
@@ -398,7 +367,7 @@
             throws TransportNotAvailableException {
         IBackupTransport transport = mTransport;
         if (transport == null) {
-            log(Log.ERROR, caller, "Transport not connected");
+            log(Priority.ERROR, caller, "Transport not connected");
             throw new TransportNotAvailableException();
         }
         return transport;
@@ -413,12 +382,92 @@
                 + "}";
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        synchronized (mStateLock) {
+            mCloseGuard.warnIfOpen();
+            if (mState >= State.BOUND_AND_CONNECTING) {
+                String callerLogString = "TransportClient.finalize()";
+                log(
+                        Priority.ERROR,
+                        callerLogString,
+                        "Dangling TransportClient created in [" + mCreatorLogString + "] being "
+                                + "GC'ed. Left bound, unbinding...");
+                try {
+                    unbind(callerLogString);
+                } catch (IllegalStateException e) {
+                    // May throw because there may be a race between this method being called and
+                    // the framework calling any method on the connection with the weak reference
+                    // there already cleared. In this case the connection will unbind before this
+                    // is called. This is fine.
+                }
+            }
+        }
+    }
+
+    private void onServiceConnected(IBinder binder) {
+        IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
+        synchronized (mStateLock) {
+            checkStateIntegrityLocked();
+
+            if (mState != State.UNUSABLE) {
+                log(Priority.DEBUG, "Transport connected");
+                setStateLocked(State.CONNECTED, transport);
+                notifyListenersAndClearLocked(transport);
+            }
+        }
+    }
+
+    /**
+     * If we are called here the TransportClient becomes UNUSABLE. After one of these calls, if a
+     * binding happen again the new service can be a different instance. Since transports are
+     * stateful, we don't want a new instance responding for an old instance's state.
+     */
+    private void onServiceDisconnected() {
+        synchronized (mStateLock) {
+            log(Priority.ERROR, "Service disconnected: client UNUSABLE");
+            setStateLocked(State.UNUSABLE, null);
+            // After unbindService() no calls back to mConnection
+            mContext.unbindService(mConnection);
+        }
+    }
+
+    /**
+     * If we are called here the TransportClient becomes UNUSABLE for the same reason as in {@link
+     * #onServiceDisconnected()}.
+     */
+    private void onBindingDied() {
+        synchronized (mStateLock) {
+            checkStateIntegrityLocked();
+
+            log(Priority.ERROR, "Binding died: client UNUSABLE");
+            // After unbindService() no calls back to mConnection
+            switch (mState) {
+                case State.UNUSABLE:
+                    break;
+                case State.IDLE:
+                    log(Priority.ERROR, "Unexpected state transition IDLE => UNUSABLE");
+                    setStateLocked(State.UNUSABLE, null);
+                    break;
+                case State.BOUND_AND_CONNECTING:
+                    setStateLocked(State.UNUSABLE, null);
+                    mContext.unbindService(mConnection);
+                    notifyListenersAndClearLocked(null);
+                    break;
+                case State.CONNECTED:
+                    setStateLocked(State.UNUSABLE, null);
+                    mContext.unbindService(mConnection);
+                    break;
+            }
+        }
+    }
+
     private void notifyListener(
             TransportConnectionListener listener,
             @Nullable IBackupTransport transport,
             String caller) {
         String transportString = (transport != null) ? "IBackupTransport" : "null";
-        log(Log.INFO, "Notifying [" + caller + "] transport = " + transportString);
+        log(Priority.INFO, "Notifying [" + caller + "] transport = " + transportString);
         mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
     }
 
@@ -434,7 +483,7 @@
 
     @GuardedBy("mStateLock")
     private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
-        log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
+        log(Priority.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
         onStateTransition(mState, state);
         mState = state;
         mTransport = transport;
@@ -503,7 +552,7 @@
 
     private void checkState(boolean assertion, String message) {
         if (!assertion) {
-            log(Log.ERROR, message);
+            log(Priority.ERROR, message);
         }
     }
 
@@ -560,9 +609,63 @@
     @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED})
     @Retention(RetentionPolicy.SOURCE)
     private @interface State {
+        // Constant values MUST be in order
         int UNUSABLE = 0;
         int IDLE = 1;
         int BOUND_AND_CONNECTING = 2;
         int CONNECTED = 3;
     }
+
+    /**
+     * This class is a proxy to TransportClient methods that doesn't hold a strong reference to the
+     * TransportClient, allowing it to be GC'ed. If the reference was lost it logs a message.
+     */
+    private static class TransportConnection implements ServiceConnection {
+        private final Context mContext;
+        private final WeakReference<TransportClient> mTransportClientRef;
+
+        private TransportConnection(Context context, TransportClient transportClient) {
+            mContext = context;
+            mTransportClientRef = new WeakReference<>(transportClient);
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName transportComponent, IBinder binder) {
+            TransportClient transportClient = mTransportClientRef.get();
+            if (transportClient == null) {
+                referenceLost("TransportConnection.onServiceConnected()");
+                return;
+            }
+            transportClient.onServiceConnected(binder);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName transportComponent) {
+            TransportClient transportClient = mTransportClientRef.get();
+            if (transportClient == null) {
+                referenceLost("TransportConnection.onServiceDisconnected()");
+                return;
+            }
+            transportClient.onServiceDisconnected();
+        }
+
+        @Override
+        public void onBindingDied(ComponentName transportComponent) {
+            TransportClient transportClient = mTransportClientRef.get();
+            if (transportClient == null) {
+                referenceLost("TransportConnection.onBindingDied()");
+                return;
+            }
+            transportClient.onBindingDied();
+        }
+
+        /** @see TransportClient#finalize() */
+        private void referenceLost(String caller) {
+            mContext.unbindService(this);
+            TransportUtils.log(
+                    Priority.INFO,
+                    TAG,
+                    caller + " called but TransportClient reference has been GC'ed");
+        }
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
index 1132bce..4041932 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
@@ -22,8 +22,10 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.util.Log;
+
 import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.TransportUtils.Priority;
+
 import java.io.PrintWriter;
 import java.util.Map;
 import java.util.WeakHashMap;
@@ -63,11 +65,14 @@
                             mContext,
                             bindIntent,
                             transportComponent,
-                            Integer.toString(mTransportClientsCreated));
+                            Integer.toString(mTransportClientsCreated),
+                            caller);
             mTransportClientsCallerMap.put(transportClient, caller);
             mTransportClientsCreated++;
             TransportUtils.log(
-                    Log.DEBUG, TAG, formatMessage(null, caller, "Retrieving " + transportClient));
+                    Priority.DEBUG,
+                    TAG,
+                    formatMessage(null, caller, "Retrieving " + transportClient));
             return transportClient;
         }
     }
@@ -82,9 +87,12 @@
      */
     public void disposeOfTransportClient(TransportClient transportClient, String caller) {
         transportClient.unbind(caller);
+        transportClient.markAsDisposed();
         synchronized (mTransportClientsLock) {
             TransportUtils.log(
-                    Log.DEBUG, TAG, formatMessage(null, caller, "Disposing of " + transportClient));
+                    Priority.DEBUG,
+                    TAG,
+                    formatMessage(null, caller, "Disposing of " + transportClient));
             mTransportClientsCallerMap.remove(transportClient);
         }
     }
diff --git a/services/backup/java/com/android/server/backup/transport/TransportUtils.java b/services/backup/java/com/android/server/backup/transport/TransportUtils.java
index 56b2d44..766d77b 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportUtils.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportUtils.java
@@ -16,6 +16,7 @@
 
 package com.android.server.backup.transport;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.os.DeadObjectException;
 import android.util.Log;
@@ -23,6 +24,9 @@
 
 import com.android.internal.backup.IBackupTransport;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /** Utility methods for transport-related operations. */
 public class TransportUtils {
     private static final String TAG = "TransportUtils";
@@ -34,14 +38,16 @@
     public static IBackupTransport checkTransportNotNull(@Nullable IBackupTransport transport)
             throws TransportNotAvailableException {
         if (transport == null) {
-            log(Log.ERROR, TAG, "Transport not available");
+            log(Priority.ERROR, TAG, "Transport not available");
             throw new TransportNotAvailableException();
         }
         return transport;
     }
 
-    static void log(int priority, String tag, String message) {
-        if (Log.isLoggable(tag, priority)) {
+    static void log(@Priority int priority, String tag, String message) {
+        if (priority == Priority.WTF) {
+            Slog.wtf(tag, message);
+        } else if (Log.isLoggable(tag, priority)) {
             Slog.println(priority, tag, message);
         }
     }
@@ -57,5 +63,20 @@
         return string.append(message).toString();
     }
 
+    /**
+     * Create our own constants so we can log WTF using the same APIs. Except for {@link
+     * Priority#WTF} all the others have the same value, so can be used directly
+     */
+    @IntDef({Priority.VERBOSE, Priority.DEBUG, Priority.INFO, Priority.WARN, Priority.WTF})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface Priority {
+        int VERBOSE = Log.VERBOSE;
+        int DEBUG = Log.DEBUG;
+        int INFO = Log.INFO;
+        int WARN = Log.WARN;
+        int ERROR = Log.ERROR;
+        int WTF = -1;
+    }
+
     private TransportUtils() {}
 }
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 15c0f3c..342b48e 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -82,6 +82,7 @@
 import java.util.TreeSet;
 import java.util.function.Predicate;
 
+import static android.app.AlarmManager.FLAG_ALLOW_WHILE_IDLE;
 import static android.app.AlarmManager.RTC_WAKEUP;
 import static android.app.AlarmManager.RTC;
 import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP;
@@ -175,7 +176,6 @@
     long mNextNonWakeupDeliveryTime;
     long mLastTimeChangeClockTime;
     long mLastTimeChangeRealtime;
-    long mAllowWhileIdleMinTime;
     int mNumTimeChanged;
 
     // Bookkeeping about the identity of the "System UI" package, determined at runtime.
@@ -199,6 +199,12 @@
      */
     final SparseLongArray mLastAllowWhileIdleDispatch = new SparseLongArray();
 
+    /**
+     * For each uid, we store whether the last allow-while-idle alarm was dispatched while
+     * the uid was in foreground or not. We will use the allow_while_idle_short_time in such cases.
+     */
+    final SparseBooleanArray mUseAllowWhileIdleShortTime = new SparseBooleanArray();
+
     final static class IdleDispatchEntry {
         int uid;
         String pkg;
@@ -242,7 +248,6 @@
         private static final String KEY_ALLOW_WHILE_IDLE_WHITELIST_DURATION
                 = "allow_while_idle_whitelist_duration";
         private static final String KEY_LISTENER_TIMEOUT = "listener_timeout";
-        private static final String KEY_BG_RESTRICTIONS_ENABLED = "limit_bg_alarms_enabled";
 
         private static final long DEFAULT_MIN_FUTURITY = 5 * 1000;
         private static final long DEFAULT_MIN_INTERVAL = 60 * 1000;
@@ -277,7 +282,6 @@
 
         public Constants(Handler handler) {
             super(handler);
-            updateAllowWhileIdleMinTimeLocked();
             updateAllowWhileIdleWhitelistDurationLocked();
         }
 
@@ -288,11 +292,6 @@
             updateConstants();
         }
 
-        public void updateAllowWhileIdleMinTimeLocked() {
-            mAllowWhileIdleMinTime = mPendingIdleUntil != null
-                    ? ALLOW_WHILE_IDLE_LONG_TIME : ALLOW_WHILE_IDLE_SHORT_TIME;
-        }
-
         public void updateAllowWhileIdleWhitelistDurationLocked() {
             if (mLastAllowWhileIdleWhitelistDuration != ALLOW_WHILE_IDLE_WHITELIST_DURATION) {
                 mLastAllowWhileIdleWhitelistDuration = ALLOW_WHILE_IDLE_WHITELIST_DURATION;
@@ -330,7 +329,6 @@
                 LISTENER_TIMEOUT = mParser.getLong(KEY_LISTENER_TIMEOUT,
                         DEFAULT_LISTENER_TIMEOUT);
 
-                updateAllowWhileIdleMinTimeLocked();
                 updateAllowWhileIdleWhitelistDurationLocked();
             }
         }
@@ -967,9 +965,6 @@
             }
         }
 
-        // Make sure we are using the correct ALLOW_WHILE_IDLE min time.
-        mConstants.updateAllowWhileIdleMinTimeLocked();
-
         // Reschedule everything.
         rescheduleKernelAlarmsLocked();
         updateNextAlarmClockLocked();
@@ -1421,7 +1416,6 @@
                 return;
             }
         }
-
         if (RECORD_DEVICE_IDLE_ALARMS) {
             if ((a.flags & AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
                 IdleDispatchEntry ent = new IdleDispatchEntry();
@@ -1472,7 +1466,6 @@
             }
 
             mPendingIdleUntil = a;
-            mConstants.updateAllowWhileIdleMinTimeLocked();
             needRebatch = true;
         } else if ((a.flags&AlarmManager.FLAG_WAKE_FROM_IDLE) != 0) {
             if (mNextWakeFromIdle == null || mNextWakeFromIdle.whenElapsed > a.whenElapsed) {
@@ -1751,6 +1744,15 @@
             if (!blocked) {
                 pw.println("    none");
             }
+            pw.print("  mUseAllowWhileIdleShortTime: [");
+            for (int i = 0; i < mUseAllowWhileIdleShortTime.size(); i++) {
+                if (mUseAllowWhileIdleShortTime.valueAt(i)) {
+                    UserHandle.formatUid(pw, mUseAllowWhileIdleShortTime.keyAt(i));
+                    pw.print(" ");
+                }
+            }
+            pw.println("]");
+
             if (mPendingIdleUntil != null || mPendingWhileIdleAlarms.size() > 0) {
                 pw.println();
                 pw.println("    Idle mode state:");
@@ -1803,9 +1805,6 @@
                 pw.println();
             }
 
-            pw.print("  mAllowWhileIdleMinTime=");
-            TimeUtils.formatDuration(mAllowWhileIdleMinTime, pw);
-            pw.println();
             if (mLastAllowWhileIdleDispatch.size() > 0) {
                 pw.println("  Last allow while idle dispatch times:");
                 for (int i=0; i<mLastAllowWhileIdleDispatch.size(); i++) {
@@ -2072,8 +2071,6 @@
                 f.writeToProto(proto, AlarmManagerServiceProto.OUTSTANDING_DELIVERIES);
             }
 
-            proto.write(AlarmManagerServiceProto.ALLOW_WHILE_IDLE_MIN_DURATION_MS,
-                    mAllowWhileIdleMinTime);
             for (int i = 0; i < mLastAllowWhileIdleDispatch.size(); ++i) {
                 final long token = proto.start(
                         AlarmManagerServiceProto.LAST_ALLOW_WHILE_IDLE_DISPATCH_TIMES);
@@ -2739,6 +2736,7 @@
     }
 
     private boolean isBackgroundRestricted(Alarm alarm) {
+        final boolean allowWhileIdle = (alarm.flags & FLAG_ALLOW_WHILE_IDLE) != 0;
         if (alarm.alarmClock != null) {
             // Don't block alarm clocks
             return false;
@@ -2751,7 +2749,8 @@
         final String sourcePackage =
                 (alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName;
         final int sourceUid = alarm.creatorUid;
-        return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage);
+        return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage,
+                allowWhileIdle);
     }
 
     private native long init();
@@ -2785,8 +2784,21 @@
                 if ((alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0) {
                     // If this is an ALLOW_WHILE_IDLE alarm, we constrain how frequently the app can
                     // schedule such alarms.
-                    long lastTime = mLastAllowWhileIdleDispatch.get(alarm.uid, 0);
-                    long minTime = lastTime + mAllowWhileIdleMinTime;
+                    final long lastTime = mLastAllowWhileIdleDispatch.get(alarm.creatorUid, 0);
+                    final boolean dozing = mPendingIdleUntil != null;
+                    final boolean ebs = mForceAppStandbyTracker.isForceAllAppsStandbyEnabled();
+                    final long minTime;
+                    if (!dozing && !ebs) {
+                        minTime = lastTime + mConstants.ALLOW_WHILE_IDLE_SHORT_TIME;
+                    } else if (dozing) {
+                        minTime = lastTime + mConstants.ALLOW_WHILE_IDLE_LONG_TIME;
+                    } else if (mUseAllowWhileIdleShortTime.get(alarm.creatorUid)) {
+                        // if the last allow-while-idle went off while uid was fg, or the uid
+                        // recently came into fg, don't block the alarm for long.
+                        minTime = lastTime + mConstants.ALLOW_WHILE_IDLE_SHORT_TIME;
+                    } else {
+                        minTime = lastTime + mConstants.ALLOW_WHILE_IDLE_LONG_TIME;
+                    }
                     if (nowELAPSED < minTime) {
                         // Whoops, it hasn't been long enough since the last ALLOW_WHILE_IDLE
                         // alarm went off for this app.  Reschedule the alarm to be in the
@@ -3526,6 +3538,7 @@
 
         @Override public void onUidGone(int uid, boolean disabled) {
             synchronized (mLock) {
+                mUseAllowWhileIdleShortTime.delete(uid);
                 if (disabled) {
                     removeForStoppedLocked(uid);
                 }
@@ -3533,6 +3546,9 @@
         }
 
         @Override public void onUidActive(int uid) {
+            synchronized (mLock) {
+                mUseAllowWhileIdleShortTime.put(uid, true);
+            }
         }
 
         @Override public void onUidIdle(int uid, boolean disabled) {
@@ -3547,7 +3563,6 @@
         }
     };
 
-
     private final Listener mForceAppStandbyListener = new Listener() {
         @Override
         public void unblockAllUnrestrictedAlarms() {
@@ -3829,7 +3844,12 @@
 
             if (allowWhileIdle) {
                 // Record the last time this uid handled an ALLOW_WHILE_IDLE alarm.
-                mLastAllowWhileIdleDispatch.put(alarm.uid, nowELAPSED);
+                mLastAllowWhileIdleDispatch.put(alarm.creatorUid, nowELAPSED);
+                if (mForceAppStandbyTracker.isInForeground(alarm.creatorUid)) {
+                    mUseAllowWhileIdleShortTime.put(alarm.creatorUid, true);
+                } else {
+                    mUseAllowWhileIdleShortTime.put(alarm.creatorUid, false);
+                }
                 if (RECORD_DEVICE_IDLE_ALARMS) {
                     IdleDispatchEntry ent = new IdleDispatchEntry();
                     ent.uid = alarm.uid;
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 145b307..3bfa31a 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -140,6 +140,7 @@
 import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.MockableSystemProperties;
+import com.android.server.connectivity.MultipathPolicyTracker;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkDiagnostics;
 import com.android.server.connectivity.NetworkMonitor;
@@ -511,6 +512,9 @@
     @VisibleForTesting
     final MultinetworkPolicyTracker mMultinetworkPolicyTracker;
 
+    @VisibleForTesting
+    final MultipathPolicyTracker mMultipathPolicyTracker;
+
     /**
      * Implements support for the legacy "one network per network type" model.
      *
@@ -894,6 +898,8 @@
                 mContext, mHandler, () -> rematchForAvoidBadWifiUpdate());
         mMultinetworkPolicyTracker.start();
 
+        mMultipathPolicyTracker = new MultipathPolicyTracker(mContext, mHandler);
+
         mDnsManager = new DnsManager(mContext, mNetd, mSystemProperties);
         registerPrivateDnsSettingsCallbacks();
     }
@@ -1974,6 +1980,9 @@
         pw.println();
         dumpAvoidBadWifiSettings(pw);
 
+        pw.println();
+        mMultipathPolicyTracker.dump(pw);
+
         if (argsContain(args, SHORT_ARG) == false) {
             pw.println();
             synchronized (mValidationLogs) {
@@ -2891,6 +2900,11 @@
             return ConnectivityManager.MULTIPATH_PREFERENCE_UNMETERED;
         }
 
+        Integer networkPreference = mMultipathPolicyTracker.getMultipathPreference(network);
+        if (networkPreference != null) {
+            return networkPreference;
+        }
+
         return mMultinetworkPolicyTracker.getMeteredMultipathPreference();
     }
 
@@ -2984,6 +2998,7 @@
                     for (NetworkAgentInfo nai : mNetworkAgentInfos.values()) {
                         nai.networkMonitor.systemReady = true;
                     }
+                    mMultipathPolicyTracker.start();
                     break;
                 }
                 case EVENT_REVALIDATE_NETWORK: {
diff --git a/services/core/java/com/android/server/ForceAppStandbyTracker.java b/services/core/java/com/android/server/ForceAppStandbyTracker.java
index 792fdfe..257845e 100644
--- a/services/core/java/com/android/server/ForceAppStandbyTracker.java
+++ b/services/core/java/com/android/server/ForceAppStandbyTracker.java
@@ -214,8 +214,11 @@
                 int uid, @NonNull String packageName) {
             updateJobsForUidPackage(uid, packageName);
 
-            if (!sender.areAlarmsRestricted(uid, packageName)) {
+            if (!sender.areAlarmsRestricted(uid, packageName, /*allowWhileIdle=*/ false)) {
                 unblockAlarmsForUidPackage(uid, packageName);
+            } else if (!sender.areAlarmsRestricted(uid, packageName, /*allowWhileIdle=*/ true)){
+                // we need to deliver the allow-while-idle alarms for this uid, package
+                unblockAllUnrestrictedAlarms();
             }
         }
 
@@ -706,7 +709,7 @@
                     synchronized (mLock) {
                         unblockAlarms = !mForcedAppStandbyEnabled && !mForceAllAppsStandby;
                     }
-                    for (Listener l: cloneListeners()) {
+                    for (Listener l : cloneListeners()) {
                         l.updateAllJobs();
                         if (unblockAlarms) {
                             l.unblockAllUnrestrictedAlarms();
@@ -818,9 +821,10 @@
     /**
      * @return whether alarms should be restricted for a UID package-name.
      */
-    public boolean areAlarmsRestricted(int uid, @NonNull String packageName) {
+    public boolean areAlarmsRestricted(int uid, @NonNull String packageName,
+            boolean allowWhileIdle) {
         return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false,
-                /* exemptOnBatterySaver =*/ false);
+                /* exemptOnBatterySaver =*/ allowWhileIdle);
     }
 
     /**
@@ -879,7 +883,6 @@
     /**
      * @return whether force all apps standby is enabled or not.
      *
-     * Note clients normally shouldn't need to access it.
      */
     boolean isForceAllAppsStandbyEnabled() {
         synchronized (mLock) {
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 9aa588f..bd93b179 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -67,6 +67,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -829,7 +830,7 @@
             }
             mAllowedResolutionLevel = getAllowedResolutionLevel(pid, uid);
             mIdentity = new Identity(uid, pid, packageName);
-            if (workSource != null && workSource.size() <= 0) {
+            if (workSource != null && workSource.isEmpty()) {
                 workSource = null;
             }
             mWorkSource = workSource;
@@ -1814,13 +1815,11 @@
 
                         if (locationRequest.getInterval() <= thresholdInterval) {
                             if (record.mReceiver.mWorkSource != null
-                                    && record.mReceiver.mWorkSource.size() > 0
-                                    && record.mReceiver.mWorkSource.getName(0) != null) {
-                                // Assign blame to another work source.
-                                // Can only assign blame if the WorkSource contains names.
+                                    && isValidWorkSource(record.mReceiver.mWorkSource)) {
                                 worksource.add(record.mReceiver.mWorkSource);
                             } else {
-                                // Assign blame to caller.
+                                // Assign blame to caller if there's no WorkSource associated with
+                                // the request or if it's invalid.
                                 worksource.add(
                                         record.mReceiver.mIdentity.mUid,
                                         record.mReceiver.mIdentity.mPackageName);
@@ -1835,6 +1834,23 @@
         p.setRequest(providerRequest, worksource);
     }
 
+    /**
+     * Whether a given {@code WorkSource} associated with a Location request is valid.
+     */
+    private static boolean isValidWorkSource(WorkSource workSource) {
+        if (workSource.size() > 0) {
+            // If the WorkSource has one or more non-chained UIDs, make sure they're accompanied
+            // by tags.
+            return workSource.getName(0) != null;
+        } else {
+            // For now, make sure callers have supplied an attribution tag for use with
+            // AppOpsManager. This might be relaxed in the future.
+            final ArrayList<WorkChain> workChains = workSource.getWorkChains();
+            return workChains != null && !workChains.isEmpty() &&
+                    workChains.get(0).getAttributionTag() != null;
+        }
+    }
+
     @Override
     public String[] getBackgroundThrottlingWhitelist() {
         synchronized (mLock) {
@@ -2057,7 +2073,7 @@
         checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel,
                 request.getProvider());
         WorkSource workSource = request.getWorkSource();
-        if (workSource != null && workSource.size() > 0) {
+        if (workSource != null && !workSource.isEmpty()) {
             checkDeviceStatsAllowed();
         }
         boolean hideFromAppOps = request.getHideFromAppOps();
diff --git a/services/core/java/com/android/server/MultipathPolicyTracker.java b/services/core/java/com/android/server/MultipathPolicyTracker.java
new file mode 100644
index 0000000..9e0a230
--- /dev/null
+++ b/services/core/java/com/android/server/MultipathPolicyTracker.java
@@ -0,0 +1,361 @@
+/*
+ * 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.server.connectivity;
+
+import android.app.usage.NetworkStatsManager;
+import android.app.usage.NetworkStatsManager.UsageCallback;
+import android.content.Context;
+import android.net.INetworkStatsService;
+import android.net.INetworkPolicyManager;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkPolicyManager;
+import android.net.NetworkRequest;
+import android.net.NetworkStats;
+import android.net.NetworkTemplate;
+import android.net.StringNetworkSpecifier;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.TelephonyManager;
+import android.util.DebugUtils;
+import android.util.Slog;
+
+import java.util.Calendar;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
+import com.android.server.net.NetworkPolicyManagerInternal;
+
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_HANDOVER;
+import static android.net.ConnectivityManager.MULTIPATH_PREFERENCE_RELIABILITY;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.provider.Settings.Global.NETWORK_AVOID_BAD_WIFI;
+import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE;
+import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
+
+/**
+ * Manages multipath data budgets.
+ *
+ * Informs the return value of ConnectivityManager#getMultipathPreference() based on:
+ * - The user's data plan, as returned by getSubscriptionOpportunisticQuota().
+ * - The amount of data usage that occurs on mobile networks while they are not the system default
+ *   network (i.e., when the app explicitly selected such networks).
+ *
+ * Currently, quota is determined on a daily basis, from midnight to midnight local time.
+ *
+ * @hide
+ */
+public class MultipathPolicyTracker {
+    private static String TAG = MultipathPolicyTracker.class.getSimpleName();
+
+    private static final boolean DBG = false;
+
+    private final Context mContext;
+    private final Handler mHandler;
+
+    private ConnectivityManager mCM;
+    private NetworkStatsManager mStatsManager;
+    private NetworkPolicyManager mNPM;
+    private TelephonyManager mTelephonyManager;
+    private INetworkStatsService mStatsService;
+
+    private NetworkCallback mMobileNetworkCallback;
+    private NetworkPolicyManager.Listener mPolicyListener;
+
+    // STOPSHIP: replace this with a configurable mechanism.
+    private static final long DEFAULT_DAILY_MULTIPATH_QUOTA = 2_500_000;
+
+    private volatile int mMeteredMultipathPreference;
+
+    public MultipathPolicyTracker(Context ctx, Handler handler) {
+        mContext = ctx;
+        mHandler = handler;
+        // Because we are initialized by the ConnectivityService constructor, we can't touch any
+        // connectivity APIs. Service initialization is done in start().
+    }
+
+    public void start() {
+        mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        mNPM = (NetworkPolicyManager) mContext.getSystemService(Context.NETWORK_POLICY_SERVICE);
+        mStatsManager = (NetworkStatsManager) mContext.getSystemService(
+                Context.NETWORK_STATS_SERVICE);
+        mStatsService = INetworkStatsService.Stub.asInterface(
+                ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+
+        registerTrackMobileCallback();
+        registerNetworkPolicyListener();
+    }
+
+    public void shutdown() {
+        maybeUnregisterTrackMobileCallback();
+        unregisterNetworkPolicyListener();
+        for (MultipathTracker t : mMultipathTrackers.values()) {
+            t.shutdown();
+        }
+        mMultipathTrackers.clear();
+    }
+
+    // Called on an arbitrary binder thread.
+    public Integer getMultipathPreference(Network network) {
+        MultipathTracker t = mMultipathTrackers.get(network);
+        if (t != null) {
+            return t.getMultipathPreference();
+        }
+        return null;
+    }
+
+    // Track information on mobile networks as they come and go.
+    class MultipathTracker {
+        final Network network;
+        final int subId;
+        final String subscriberId;
+
+        private long mQuota;
+        /** Current multipath budget. Nonzero iff we have budget and a UsageCallback is armed. */
+        private long mMultipathBudget;
+        private final NetworkTemplate mNetworkTemplate;
+        private final UsageCallback mUsageCallback;
+
+        public MultipathTracker(Network network, NetworkCapabilities nc) {
+            this.network = network;
+            try {
+                subId = Integer.parseInt(
+                        ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString());
+            } catch (ClassCastException | NullPointerException | NumberFormatException e) {
+                throw new IllegalStateException(String.format(
+                        "Can't get subId from mobile network %s (%s): %s",
+                        network, nc, e.getMessage()));
+            }
+
+            TelephonyManager tele = (TelephonyManager) mContext.getSystemService(
+                    Context.TELEPHONY_SERVICE);
+            if (tele == null) {
+                throw new IllegalStateException(String.format("Missing TelephonyManager"));
+            }
+            tele = tele.createForSubscriptionId(subId);
+            if (tele == null) {
+                throw new IllegalStateException(String.format(
+                        "Can't get TelephonyManager for subId %d", subId));
+            }
+
+            subscriberId = tele.getSubscriberId();
+            mNetworkTemplate = new NetworkTemplate(
+                    NetworkTemplate.MATCH_MOBILE_ALL, subscriberId, new String[] { subscriberId },
+                    null, NetworkStats.METERED_ALL, NetworkStats.ROAMING_ALL,
+                    NetworkStats.DEFAULT_NETWORK_NO);
+            mUsageCallback = new UsageCallback() {
+                @Override
+                public void onThresholdReached(int networkType, String subscriberId) {
+                    if (DBG) Slog.d(TAG, "onThresholdReached for network " + network);
+                    mMultipathBudget = 0;
+                    updateMultipathBudget();
+                }
+            };
+
+            updateMultipathBudget();
+        }
+
+        private long getDailyNonDefaultDataUsage() {
+            Calendar start = Calendar.getInstance();
+            Calendar end = (Calendar) start.clone();
+            start.set(Calendar.HOUR_OF_DAY, 0);
+            start.set(Calendar.MINUTE, 0);
+            start.set(Calendar.SECOND, 0);
+            start.set(Calendar.MILLISECOND, 0);
+
+            long bytes;
+            try {
+                // TODO: Consider using NetworkStatsManager.getSummaryForDevice instead.
+                bytes = mStatsService.getNetworkTotalBytes(mNetworkTemplate,
+                        start.getTimeInMillis(), end.getTimeInMillis());
+                if (DBG) Slog.w(TAG, "Non-default data usage: " + bytes);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Can't fetch daily data usage: " + e);
+                bytes = -1;
+            } catch (IllegalStateException e) {
+                // Bandwidth control disabled?
+                bytes = -1;
+            }
+            return bytes;
+        }
+
+        void updateMultipathBudget() {
+            NetworkPolicyManagerInternal npms = LocalServices.getService(
+                    NetworkPolicyManagerInternal.class);
+            long quota = npms.getSubscriptionOpportunisticQuota(this.network, QUOTA_TYPE_MULTIPATH);
+            if (DBG) Slog.d(TAG, "Opportunistic quota from data plan: " + quota + " bytes");
+
+            if (quota == 0) {
+                // STOPSHIP: replace this with a configurable mechanism.
+                quota = DEFAULT_DAILY_MULTIPATH_QUOTA;
+                if (DBG) Slog.d(TAG, "Setting quota: " + quota + " bytes");
+            }
+
+            if (haveMultipathBudget() && quota == mQuota) {
+                // If we already have a usage callback pending , there's no need to re-register it
+                // if the quota hasn't changed. The callback will simply fire as expected when the
+                // budget is spent. Also: if we re-register the callback when we're below the
+                // UsageCallback's minimum value of 2MB, we'll overshoot the budget.
+                if (DBG) Slog.d(TAG, "Quota still " + quota + ", not updating.");
+                return;
+            }
+            mQuota = quota;
+
+            long usage = getDailyNonDefaultDataUsage();
+            long budget = Math.max(0, quota - usage);
+            if (budget > 0) {
+                if (DBG) Slog.d(TAG, "Setting callback for " + budget +
+                        " bytes on network " + network);
+                registerUsageCallback(budget);
+            } else {
+                maybeUnregisterUsageCallback();
+            }
+        }
+
+        public int getMultipathPreference() {
+            if (haveMultipathBudget()) {
+                return MULTIPATH_PREFERENCE_HANDOVER | MULTIPATH_PREFERENCE_RELIABILITY;
+            }
+            return 0;
+        }
+
+        // For debugging only.
+        public long getQuota() {
+            return mQuota;
+        }
+
+        // For debugging only.
+        public long getMultipathBudget() {
+            return mMultipathBudget;
+        }
+
+        private boolean haveMultipathBudget() {
+            return mMultipathBudget > 0;
+        }
+
+        private void registerUsageCallback(long budget) {
+            maybeUnregisterUsageCallback();
+            mStatsManager.registerUsageCallback(mNetworkTemplate, TYPE_MOBILE, budget,
+                    mUsageCallback, mHandler);
+            mMultipathBudget = budget;
+        }
+
+        private void maybeUnregisterUsageCallback() {
+            if (haveMultipathBudget()) {
+                if (DBG) Slog.d(TAG, "Unregistering callback, budget was " + mMultipathBudget);
+                mStatsManager.unregisterUsageCallback(mUsageCallback);
+                mMultipathBudget = 0;
+            }
+        }
+
+        void shutdown() {
+            maybeUnregisterUsageCallback();
+        }
+    }
+
+    // Only ever updated on the handler thread. Accessed from other binder threads to retrieve
+    // the tracker for a specific network.
+    private final ConcurrentHashMap <Network, MultipathTracker> mMultipathTrackers =
+            new ConcurrentHashMap<>();
+
+    // TODO: this races with app code that might respond to onAvailable() by immediately calling
+    // getMultipathPreference. Fix this by adding to ConnectivityService the ability to directly
+    // invoke NetworkCallbacks on tightly-coupled classes such as this one which run on its
+    // handler thread.
+    private void registerTrackMobileCallback() {
+        final NetworkRequest request = new NetworkRequest.Builder()
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .addTransportType(TRANSPORT_CELLULAR)
+                .build();
+        mMobileNetworkCallback = new ConnectivityManager.NetworkCallback() {
+            @Override
+            public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+                MultipathTracker existing = mMultipathTrackers.get(network);
+                if (existing != null) {
+                    existing.updateMultipathBudget();
+                    return;
+                }
+
+                try {
+                    mMultipathTrackers.put(network, new MultipathTracker(network, nc));
+                } catch (IllegalStateException e) {
+                    Slog.e(TAG, "Can't track mobile network " + network + ": " + e.getMessage());
+                }
+                if (DBG) Slog.d(TAG, "Tracking mobile network " + network);
+            }
+
+            @Override
+            public void onLost(Network network) {
+                MultipathTracker existing = mMultipathTrackers.get(network);
+                if (existing != null) {
+                    existing.shutdown();
+                    mMultipathTrackers.remove(network);
+                }
+                if (DBG) Slog.d(TAG, "No longer tracking mobile network " + network);
+            }
+        };
+
+        mCM.registerNetworkCallback(request, mMobileNetworkCallback, mHandler);
+    }
+
+    private void maybeUnregisterTrackMobileCallback() {
+        if (mMobileNetworkCallback != null) {
+            mCM.unregisterNetworkCallback(mMobileNetworkCallback);
+        }
+        mMobileNetworkCallback = null;
+    }
+
+    private void registerNetworkPolicyListener() {
+        mPolicyListener = new NetworkPolicyManager.Listener() {
+            @Override
+            public void onMeteredIfacesChanged(String[] meteredIfaces) {
+                // Dispatched every time opportunistic quota is recalculated.
+                mHandler.post(() -> {
+                    for (MultipathTracker t : mMultipathTrackers.values()) {
+                        t.updateMultipathBudget();
+                    }
+                });
+            }
+        };
+        mNPM.registerListener(mPolicyListener);
+    }
+
+    private void unregisterNetworkPolicyListener() {
+        mNPM.unregisterListener(mPolicyListener);
+    }
+
+    public void dump(IndentingPrintWriter pw) {
+        // Do not use in production. Access to class data is only safe on the handler thrad.
+        pw.println("MultipathPolicyTracker:");
+        pw.increaseIndent();
+        for (MultipathTracker t : mMultipathTrackers.values()) {
+            pw.println(String.format("Network %s: quota %d, budget %d. Preference: %s",
+                    t.network, t.getQuota(), t.getMultipathBudget(),
+                    DebugUtils.flagsToString(ConnectivityManager.class, "MULTIPATH_PREFERENCE_",
+                            t.getMultipathPreference())));
+        }
+        pw.decreaseIndent();
+    }
+}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index fedd3d2..364d5d5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3463,6 +3463,7 @@
     final void showAppWarningsIfNeededLocked(ActivityRecord r) {
         mAppWarnings.showUnsupportedCompileSdkDialogIfNeeded(r);
         mAppWarnings.showUnsupportedDisplaySizeDialogIfNeeded(r);
+        mAppWarnings.showDeprecatedTargetDialogIfNeeded(r);
     }
 
     private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
@@ -4060,10 +4061,15 @@
 
             if (app.info.isPrivilegedApp() &&
                     SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false)) {
-                runtimeFlags |= Zygote.DISABLE_VERIFIER;
                 runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
             }
 
+            if (app.info.isAllowedToUseHiddenApi()) {
+                // This app is allowed to use undocumented and private APIs. Set
+                // up its runtime with the appropriate flag.
+                runtimeFlags |= Zygote.DISABLE_HIDDEN_API_CHECKS;
+            }
+
             String invokeWith = null;
             if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
                 // Debuggable apps may include a wrapper script with their library directory.
diff --git a/services/core/java/com/android/server/am/AppWarnings.java b/services/core/java/com/android/server/am/AppWarnings.java
index a3c0345..806e95d 100644
--- a/services/core/java/com/android/server/am/AppWarnings.java
+++ b/services/core/java/com/android/server/am/AppWarnings.java
@@ -50,6 +50,7 @@
 
     public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01;
     public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
+    public static final int FLAG_HIDE_DEPRECATED_SDK = 0x04;
 
     private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
 
@@ -61,6 +62,7 @@
 
     private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
     private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog;
+    private DeprecatedTargetSdkVersionDialog mDeprecatedTargetSdkVersionDialog;
 
     /**
      * Creates a new warning dialog manager.
@@ -126,6 +128,17 @@
     }
 
     /**
+     * Shows the "deprecated target sdk" warning, if necessary.
+     *
+     * @param r activity record for which the warning may be displayed
+     */
+    public void showDeprecatedTargetDialogIfNeeded(ActivityRecord r) {
+        if (r.appInfo.targetSdkVersion < Build.VERSION.MIN_SUPPORTED_TARGET_SDK_INT) {
+            mUiHandler.showDeprecatedTargetDialog(r);
+        }
+    }
+
+    /**
      * Called when an activity is being started.
      *
      * @param r record for the activity being started
@@ -133,6 +146,7 @@
     public void onStartActivity(ActivityRecord r) {
         showUnsupportedCompileSdkDialogIfNeeded(r);
         showUnsupportedDisplaySizeDialogIfNeeded(r);
+        showDeprecatedTargetDialogIfNeeded(r);
     }
 
     /**
@@ -237,6 +251,27 @@
     }
 
     /**
+     * Shows the "deprecated target sdk version" warning for the given application.
+     * <p>
+     * <strong>Note:</strong> Must be called on the UI thread.
+     *
+     * @param ar record for the activity that triggered the warning
+     */
+    @UiThread
+    private void showDeprecatedTargetSdkDialogUiThread(ActivityRecord ar) {
+        if (mDeprecatedTargetSdkVersionDialog != null) {
+            mDeprecatedTargetSdkVersionDialog.dismiss();
+            mDeprecatedTargetSdkVersionDialog = null;
+        }
+        if (ar != null && !hasPackageFlag(
+                ar.packageName, FLAG_HIDE_DEPRECATED_SDK)) {
+            mDeprecatedTargetSdkVersionDialog = new DeprecatedTargetSdkVersionDialog(
+                    AppWarnings.this, mUiContext, ar.info.applicationInfo);
+            mDeprecatedTargetSdkVersionDialog.show();
+        }
+    }
+
+    /**
      * Dismisses all warnings for the given package.
      * <p>
      * <strong>Note:</strong> Must be called on the UI thread.
@@ -259,6 +294,13 @@
             mUnsupportedCompileSdkDialog.dismiss();
             mUnsupportedCompileSdkDialog = null;
         }
+
+        // Hides the "deprecated target sdk version" dialog if necessary.
+        if (mDeprecatedTargetSdkVersionDialog != null && (name == null || name.equals(
+                mDeprecatedTargetSdkVersionDialog.getPackageName()))) {
+            mDeprecatedTargetSdkVersionDialog.dismiss();
+            mDeprecatedTargetSdkVersionDialog = null;
+        }
     }
 
     /**
@@ -282,7 +324,7 @@
     void setPackageFlag(String name, int flag, boolean enabled) {
         synchronized (mPackageFlags) {
             final int curFlags = getPackageFlags(name);
-            final int newFlags = enabled ? (curFlags & ~flag) : (curFlags | flag);
+            final int newFlags = enabled ? (curFlags | flag) : (curFlags & ~flag);
             if (curFlags != newFlags) {
                 if (newFlags != 0) {
                     mPackageFlags.put(name, newFlags);
@@ -311,6 +353,7 @@
         private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2;
         private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3;
         private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
+        private static final int MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG = 5;
 
         public UiHandler(Looper looper) {
             super(looper, null, true);
@@ -334,6 +377,10 @@
                     final String name = (String) msg.obj;
                     hideDialogsForPackageUiThread(name);
                 } break;
+                case MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG: {
+                    final ActivityRecord ar = (ActivityRecord) msg.obj;
+                    showDeprecatedTargetSdkDialogUiThread(ar);
+                } break;
             }
         }
 
@@ -352,6 +399,11 @@
             obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget();
         }
 
+        public void showDeprecatedTargetDialog(ActivityRecord r) {
+            removeMessages(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG);
+            obtainMessage(MSG_SHOW_DEPRECATED_TARGET_SDK_DIALOG, r).sendToTarget();
+        }
+
         public void hideDialogsForPackage(String name) {
             obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget();
         }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 04b49ba..6b380f1 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -582,17 +582,11 @@
         }
     }
 
-    public void noteStartGps(int uid) {
+    @Override
+    public void noteGpsChanged(WorkSource oldWs, WorkSource newWs) {
         enforceCallingPermission();
         synchronized (mStats) {
-            mStats.noteStartGpsLocked(uid);
-        }
-    }
-
-    public void noteStopGps(int uid) {
-        enforceCallingPermission();
-        synchronized (mStats) {
-            mStats.noteStopGpsLocked(uid);
+            mStats.noteGpsChangedLocked(oldWs, newWs);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/DeprecatedTargetSdkVersionDialog.java b/services/core/java/com/android/server/am/DeprecatedTargetSdkVersionDialog.java
new file mode 100644
index 0000000..84dca7f
--- /dev/null
+++ b/services/core/java/com/android/server/am/DeprecatedTargetSdkVersionDialog.java
@@ -0,0 +1,83 @@
+/*
+ * 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 com.android.server.am;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.SystemPropertiesProto;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+
+import com.android.internal.R;
+import com.android.server.utils.AppInstallerUtil;
+
+public class DeprecatedTargetSdkVersionDialog {
+    private final AlertDialog mDialog;
+    private final String mPackageName;
+
+    public DeprecatedTargetSdkVersionDialog(final AppWarnings manager, Context context,
+            ApplicationInfo appInfo) {
+        mPackageName = appInfo.packageName;
+
+        final PackageManager pm = context.getPackageManager();
+        final CharSequence label = appInfo.loadSafeLabel(pm);
+        final CharSequence message = context.getString(R.string.deprecated_target_sdk_message);
+
+        final AlertDialog.Builder builder = new AlertDialog.Builder(context)
+                .setPositiveButton(R.string.ok, (dialog, which) ->
+                    manager.setPackageFlag(
+                            mPackageName, AppWarnings.FLAG_HIDE_DEPRECATED_SDK, true))
+                .setMessage(message)
+                .setTitle(label);
+
+        // If we might be able to update the app, show a button.
+        final Intent installerIntent = AppInstallerUtil.createIntent(context, appInfo.packageName);
+        if (installerIntent != null) {
+            builder.setNeutralButton(R.string.deprecated_target_sdk_app_store,
+                    (dialog, which) -> {
+                        context.startActivity(installerIntent);
+                    });
+        }
+
+        // Ensure the content view is prepared.
+        mDialog = builder.create();
+        mDialog.create();
+
+        final Window window = mDialog.getWindow();
+        window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+
+        // DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
+        window.getAttributes().setTitle("DeprecatedTargetSdkVersionDialog");
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public void show() {
+        mDialog.show();
+    }
+
+    public void dismiss() {
+        mDialog.dismiss();
+    }
+}
diff --git a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
deleted file mode 100644
index f6b73b7..0000000
--- a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.connectivity;
-
-import static android.net.util.NetworkConstants.IPV4_HEADER_MIN_LEN;
-import static android.net.util.NetworkConstants.UDP_HEADER_LEN;
-
-import android.system.OsConstants;
-import android.net.ConnectivityManager;
-import android.net.NetworkUtils;
-import android.net.util.IpUtils;
-
-import java.net.Inet4Address;
-import java.net.Inet6Address;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-
-import static android.net.ConnectivityManager.PacketKeepalive.*;
-
-/**
- * Represents the actual packets that are sent by the
- * {@link android.net.ConnectivityManager.PacketKeepalive} API.
- *
- * @hide
- */
-public class KeepalivePacketData {
-    /** Protocol of the packet to send; one of the OsConstants.ETH_P_* values. */
-    public final int protocol;
-
-    /** Source IP address */
-    public final InetAddress srcAddress;
-
-    /** Destination IP address */
-    public final InetAddress dstAddress;
-
-    /** Source port */
-    public final int srcPort;
-
-    /** Destination port */
-    public final int dstPort;
-
-    /** Destination MAC address. Can change if routing changes. */
-    public byte[] dstMac;
-
-    /** Packet data. A raw byte string of packet data, not including the link-layer header. */
-    public final byte[] data;
-
-    protected KeepalivePacketData(InetAddress srcAddress, int srcPort,
-            InetAddress dstAddress, int dstPort, byte[] data) throws InvalidPacketException {
-        this.srcAddress = srcAddress;
-        this.dstAddress = dstAddress;
-        this.srcPort = srcPort;
-        this.dstPort = dstPort;
-        this.data = data;
-
-        // Check we have two IP addresses of the same family.
-        if (srcAddress == null || dstAddress == null ||
-                !srcAddress.getClass().getName().equals(dstAddress.getClass().getName())) {
-            throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
-        }
-
-        // Set the protocol.
-        if (this.dstAddress instanceof Inet4Address) {
-            this.protocol = OsConstants.ETH_P_IP;
-        } else if (this.dstAddress instanceof Inet6Address) {
-            this.protocol = OsConstants.ETH_P_IPV6;
-        } else {
-            throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
-        }
-
-        // Check the ports.
-        if (!IpUtils.isValidUdpOrTcpPort(srcPort) || !IpUtils.isValidUdpOrTcpPort(dstPort)) {
-            throw new InvalidPacketException(ERROR_INVALID_PORT);
-        }
-    }
-
-    public static class InvalidPacketException extends Exception {
-        final public int error;
-        public InvalidPacketException(int error) {
-            this.error = error;
-        }
-    }
-
-    /**
-     * Creates an IPsec NAT-T keepalive packet with the specified parameters.
-     */
-    public static KeepalivePacketData nattKeepalivePacket(
-            InetAddress srcAddress, int srcPort,
-            InetAddress dstAddress, int dstPort) throws InvalidPacketException {
-
-        if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) {
-            throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
-        }
-
-        if (dstPort != NATT_PORT) {
-            throw new InvalidPacketException(ERROR_INVALID_PORT);
-        }
-
-        int length = IPV4_HEADER_MIN_LEN + UDP_HEADER_LEN + 1;
-        ByteBuffer buf = ByteBuffer.allocate(length);
-        buf.order(ByteOrder.BIG_ENDIAN);
-        buf.putShort((short) 0x4500);             // IP version and TOS
-        buf.putShort((short) length);
-        buf.putInt(0);                            // ID, flags, offset
-        buf.put((byte) 64);                       // TTL
-        buf.put((byte) OsConstants.IPPROTO_UDP);
-        int ipChecksumOffset = buf.position();
-        buf.putShort((short) 0);                  // IP checksum
-        buf.put(srcAddress.getAddress());
-        buf.put(dstAddress.getAddress());
-        buf.putShort((short) srcPort);
-        buf.putShort((short) dstPort);
-        buf.putShort((short) (length - 20));      // UDP length
-        int udpChecksumOffset = buf.position();
-        buf.putShort((short) 0);                  // UDP checksum
-        buf.put((byte) 0xff);                     // NAT-T keepalive
-        buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
-        buf.putShort(udpChecksumOffset, IpUtils.udpChecksum(buf, 0, IPV4_HEADER_MIN_LEN));
-
-        return new KeepalivePacketData(srcAddress, srcPort, dstAddress, dstPort, buf.array());
-    }
-}
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 9e1f6b8..d24f9c9 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -18,10 +18,10 @@
 
 import com.android.internal.util.HexDump;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.connectivity.KeepalivePacketData;
 import com.android.server.connectivity.NetworkAgentInfo;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.PacketKeepalive;
+import android.net.KeepalivePacketData;
 import android.net.LinkAddress;
 import android.net.NetworkAgent;
 import android.net.NetworkUtils;
@@ -129,7 +129,7 @@
                     .append("->")
                     .append(IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort))
                     .append(" interval=" + mInterval)
-                    .append(" data=" + HexDump.toHexString(mPacket.data))
+                    .append(" packetData=" + HexDump.toHexString(mPacket.getPacket()))
                     .append(" uid=").append(mUid).append(" pid=").append(mPid)
                     .append(" ]")
                     .toString();
@@ -172,7 +172,7 @@
         }
 
         private int checkInterval() {
-            return mInterval >= 20 ? SUCCESS : ERROR_INVALID_INTERVAL;
+            return mInterval >= 10 ? SUCCESS : ERROR_INVALID_INTERVAL;
         }
 
         private int isValid() {
diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java
index ff05723..be6c4a1 100644
--- a/services/core/java/com/android/server/connectivity/Tethering.java
+++ b/services/core/java/com/android/server/connectivity/Tethering.java
@@ -506,6 +506,7 @@
         Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING);
         intent.putExtra(ConnectivityManager.EXTRA_ADD_TETHER_TYPE, type);
         intent.putExtra(ConnectivityManager.EXTRA_PROVISION_CALLBACK, receiver);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         final long ident = Binder.clearCallingIdentity();
         try {
             mContext.startActivityAsUser(intent, UserHandle.CURRENT);
diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java
index 83a3c19..f23147b 100644
--- a/services/core/java/com/android/server/job/JobServiceContext.java
+++ b/services/core/java/com/android/server/job/JobServiceContext.java
@@ -400,7 +400,7 @@
                     (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
             PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                     runningJob.getTag());
-            wl.setWorkSource(new WorkSource(runningJob.getSourceUid()));
+            wl.setWorkSource(deriveWorkSource(runningJob));
             wl.setReferenceCounted(false);
             wl.acquire();
 
@@ -419,6 +419,19 @@
         }
     }
 
+    private WorkSource deriveWorkSource(JobStatus runningJob) {
+        final int jobUid = runningJob.getSourceUid();
+        if (WorkSource.isChainedBatteryAttributionEnabled(mContext)) {
+            WorkSource workSource = new WorkSource();
+            workSource.createWorkChain()
+                    .addNode(jobUid, null)
+                    .addNode(android.os.Process.SYSTEM_UID, "JobScheduler");
+            return workSource;
+        } else {
+            return new WorkSource(jobUid);
+        }
+    }
+
     /** If the client service crashes we reschedule this job and clean up. */
     @Override
     public void onServiceDisconnected(ComponentName name) {
diff --git a/services/core/java/com/android/server/job/controllers/TimeController.java b/services/core/java/com/android/server/job/controllers/TimeController.java
index bbee0eb..a91f5a4 100644
--- a/services/core/java/com/android/server/job/controllers/TimeController.java
+++ b/services/core/java/com/android/server/job/controllers/TimeController.java
@@ -18,9 +18,11 @@
 
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.AlarmManager.OnAlarmListener;
 import android.content.Context;
+import android.os.Process;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.Slog;
@@ -52,6 +54,8 @@
     private long mNextJobExpiredElapsedMillis;
     private long mNextDelayExpiredElapsedMillis;
 
+    private final boolean mChainedAttributionEnabled;
+
     private AlarmManager mAlarmService = null;
     /** List of tracked jobs, sorted asc. by deadline */
     private final List<JobStatus> mTrackedJobs = new LinkedList<>();
@@ -71,6 +75,7 @@
 
         mNextJobExpiredElapsedMillis = Long.MAX_VALUE;
         mNextDelayExpiredElapsedMillis = Long.MAX_VALUE;
+        mChainedAttributionEnabled = WorkSource.isChainedBatteryAttributionEnabled(context);
     }
 
     /**
@@ -113,7 +118,7 @@
             maybeUpdateAlarmsLocked(
                     job.hasTimingDelayConstraint() ? job.getEarliestRunTime() : Long.MAX_VALUE,
                     job.hasDeadlineConstraint() ? job.getLatestRunTimeElapsed() : Long.MAX_VALUE,
-                    new WorkSource(job.getSourceUid(), job.getSourcePackageName()));
+                    deriveWorkSource(job.getSourceUid(), job.getSourcePackageName()));
         }
     }
 
@@ -179,9 +184,8 @@
                     break;
                 }
             }
-            setDeadlineExpiredAlarmLocked(nextExpiryTime, nextExpiryPackageName != null
-                    ? new WorkSource(nextExpiryUid, nextExpiryPackageName)
-                    : new WorkSource(nextExpiryUid));
+            setDeadlineExpiredAlarmLocked(nextExpiryTime,
+                    deriveWorkSource(nextExpiryUid, nextExpiryPackageName));
         }
     }
 
@@ -236,9 +240,20 @@
             if (ready) {
                 mStateChangedListener.onControllerStateChanged();
             }
-            setDelayExpiredAlarmLocked(nextDelayTime, nextDelayPackageName != null
-                    ? new WorkSource(nextDelayUid, nextDelayPackageName)
-                    : new WorkSource(nextDelayUid));
+            setDelayExpiredAlarmLocked(nextDelayTime,
+                    deriveWorkSource(nextDelayUid, nextDelayPackageName));
+        }
+    }
+
+    private WorkSource deriveWorkSource(int uid, @Nullable String packageName) {
+        if (mChainedAttributionEnabled) {
+            WorkSource ws = new WorkSource();
+            ws.createWorkChain()
+                    .addNode(uid, packageName)
+                    .addNode(Process.SYSTEM_UID, "JobScheduler");
+            return ws;
+        } else {
+            return packageName == null ? new WorkSource(uid) : new WorkSource(uid, packageName);
         }
     }
 
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index 48d275c..55c0f5a 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -64,6 +64,7 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.os.WorkSource.WorkChain;
 import android.provider.Settings;
 import android.provider.Telephony.Carriers;
 import android.provider.Telephony.Sms.Intents;
@@ -346,7 +347,8 @@
 
     // Current request from underlying location clients.
     private ProviderRequest mProviderRequest = null;
-    // Current list of underlying location clients.
+    // The WorkSource associated with the most recent client request (i.e, most recent call to
+    // setRequest).
     private WorkSource mWorkSource = null;
     // True if gps should be disabled (used to support battery saver mode in settings).
     private boolean mDisableGps = false;
@@ -408,6 +410,7 @@
     private final IAppOpsService mAppOpsService;
     private final IBatteryStats mBatteryStats;
 
+    // Current list of underlying location clients.
     // only modified on handler thread
     private WorkSource mClientSource = new WorkSource();
 
@@ -1345,46 +1348,80 @@
     }
 
     private void updateClientUids(WorkSource source) {
-        // Update work source.
-        WorkSource[] changes = mClientSource.setReturningDiffs(source);
-        if (changes == null) {
+        if (source.equals(mClientSource)) {
             return;
         }
-        WorkSource newWork = changes[0];
-        WorkSource goneWork = changes[1];
 
-        // Update sources that were not previously tracked.
-        if (newWork != null) {
-            int lastuid = -1;
-            for (int i = 0; i < newWork.size(); i++) {
-                try {
-                    int uid = newWork.get(i);
-                    mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
-                            AppOpsManager.OP_GPS, uid, newWork.getName(i));
-                    if (uid != lastuid) {
-                        lastuid = uid;
-                        mBatteryStats.noteStartGps(uid);
-                    }
-                } catch (RemoteException e) {
-                    Log.w(TAG, "RemoteException", e);
-                }
-            }
+        // (1) Inform BatteryStats that the list of IDs we're tracking changed.
+        try {
+            mBatteryStats.noteGpsChanged(mClientSource, source);
+        } catch (RemoteException e) {
+            Log.w(TAG, "RemoteException", e);
         }
 
-        // Update sources that are no longer tracked.
-        if (goneWork != null) {
-            int lastuid = -1;
-            for (int i = 0; i < goneWork.size(); i++) {
-                try {
-                    int uid = goneWork.get(i);
-                    mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
-                            AppOpsManager.OP_GPS, uid, goneWork.getName(i));
-                    if (uid != lastuid) {
-                        lastuid = uid;
-                        mBatteryStats.noteStopGps(uid);
+        // (2) Inform AppOps service about the list of changes to UIDs.
+
+        List<WorkChain>[] diffs = WorkSource.diffChains(mClientSource, source);
+        if (diffs != null) {
+            List<WorkChain> newChains = diffs[0];
+            List<WorkChain> goneChains = diffs[1];
+
+            if (newChains != null) {
+                for (int i = 0; i < newChains.size(); ++i) {
+                    final WorkChain newChain = newChains.get(i);
+                    try {
+                        mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+                                AppOpsManager.OP_GPS, newChain.getAttributionUid(),
+                                newChain.getAttributionTag());
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "RemoteException", e);
                     }
-                } catch (RemoteException e) {
-                    Log.w(TAG, "RemoteException", e);
+                }
+            }
+
+            if (goneChains != null) {
+                for (int i = 0; i < goneChains.size(); i++) {
+                    final WorkChain goneChain = goneChains.get(i);
+                    try {
+                        mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
+                                AppOpsManager.OP_GPS, goneChain.getAttributionUid(),
+                                goneChain.getAttributionTag());
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "RemoteException", e);
+                    }
+                }
+            }
+
+            mClientSource.transferWorkChains(source);
+        }
+
+        // Update the flat UIDs and names list and inform app-ops of all changes.
+        WorkSource[] changes = mClientSource.setReturningDiffs(source);
+        if (changes != null) {
+            WorkSource newWork = changes[0];
+            WorkSource goneWork = changes[1];
+
+            // Update sources that were not previously tracked.
+            if (newWork != null) {
+                for (int i = 0; i < newWork.size(); i++) {
+                    try {
+                        mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService),
+                                AppOpsManager.OP_GPS, newWork.get(i), newWork.getName(i));
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "RemoteException", e);
+                    }
+                }
+            }
+
+            // Update sources that are no longer tracked.
+            if (goneWork != null) {
+                for (int i = 0; i < goneWork.size(); i++) {
+                    try {
+                        mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService),
+                                AppOpsManager.OP_GPS, goneWork.get(i), goneWork.getName(i));
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "RemoteException", e);
+                    }
                 }
             }
         }
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 b6c3c66..0d567d1 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -40,6 +40,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.HexDump;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverySnapshotStorage;
@@ -455,20 +456,64 @@
     private byte[] decryptRecoveryKey(
             RecoverySessionStorage.Entry sessionEntry, byte[] encryptedClaimResponse)
             throws RemoteException, ServiceSpecificException {
+        // TODO: Remove the extensive loggings in this function
+        byte[] locallyEncryptedKey;
         try {
-            byte[] locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
+            locallyEncryptedKey = KeySyncUtils.decryptRecoveryClaimResponse(
                     sessionEntry.getKeyClaimant(),
                     sessionEntry.getVaultParams(),
                     encryptedClaimResponse);
-            return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
-        } catch (InvalidKeyException | AEADBadTagException e) {
+        } catch (InvalidKeyException e) {
+            Log.e(TAG, "Got InvalidKeyException during decrypting recovery claim response", e);
+            Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()",
+                    sessionEntry.getKeyClaimant()));
+            Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()",
+                    sessionEntry.getVaultParams()));
+            Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse));
             throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
                     "Failed to decrypt recovery key " + e.getMessage());
-
+        } catch (AEADBadTagException e) {
+            Log.e(TAG, "Got AEADBadTagException during decrypting recovery claim response", e);
+            Log.e(TAG, constructLoggingMessage("sessionEntry.getKeyClaimant()",
+                    sessionEntry.getKeyClaimant()));
+            Log.e(TAG, constructLoggingMessage("sessionEntry.getVaultParams()",
+                    sessionEntry.getVaultParams()));
+            Log.e(TAG, constructLoggingMessage("encryptedClaimResponse", encryptedClaimResponse));
+            throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
+                    "Failed to decrypt recovery key " + e.getMessage());
         } catch (NoSuchAlgorithmException e) {
             // Should never happen: all the algorithms used are required by AOSP implementations
             throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
         }
+
+        try {
+            return KeySyncUtils.decryptRecoveryKey(sessionEntry.getLskfHash(), locallyEncryptedKey);
+        } catch (InvalidKeyException e) {
+            Log.e(TAG, "Got InvalidKeyException during decrypting recovery key", e);
+            Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()",
+                    sessionEntry.getLskfHash()));
+            Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey));
+            throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
+                    "Failed to decrypt recovery key " + e.getMessage());
+        } catch (AEADBadTagException e) {
+            Log.e(TAG, "Got AEADBadTagException during decrypting recovery key", e);
+            Log.e(TAG, constructLoggingMessage("sessionEntry.getLskfHash()",
+                    sessionEntry.getLskfHash()));
+            Log.e(TAG, constructLoggingMessage("locallyEncryptedKey", locallyEncryptedKey));
+            throw new ServiceSpecificException(ERROR_DECRYPTION_FAILED,
+                    "Failed to decrypt recovery key " + e.getMessage());
+        } catch (NoSuchAlgorithmException e) {
+            // Should never happen: all the algorithms used are required by AOSP implementations
+            throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
+        }
+    }
+
+    private String constructLoggingMessage(String key, byte[] value) {
+        if (value == null) {
+            return key + " is null";
+        } else {
+            return key + ": " + HexDump.toHexString(value);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
index 824b148..4000a11 100644
--- a/services/core/java/com/android/server/media/MediaSession2Record.java
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -137,7 +137,7 @@
 
     final MediaController2 createMediaController(SessionToken2 token) {
         mControllerCallback = new ControllerCallback();
-        return new MediaController2(mContext, token, mControllerCallback, mMainExecutor);
+        return new MediaController2(mContext, token, mMainExecutor, mControllerCallback);
     }
 
     /**
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 321af43..7600e81 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -283,7 +283,18 @@
     }
 
     void onOverlayPackageRemoved(@NonNull final String packageName, final int userId) {
-        Slog.wtf(TAG, "onOverlayPackageRemoved called, but only pre-installed overlays supported");
+        try {
+            final OverlayInfo overlayInfo = mSettings.getOverlayInfo(packageName, userId);
+            if (mSettings.remove(packageName, userId)) {
+                removeIdmapIfPossible(overlayInfo);
+                if (overlayInfo.isEnabled()) {
+                    // Only trigger updates if the overlay was enabled.
+                    mListener.onOverlaysChanged(overlayInfo.targetPackageName, userId);
+                }
+            }
+        } catch (OverlayManagerSettings.BadKeyException e) {
+            Slog.e(TAG, "failed to remove overlay", e);
+        }
     }
 
     OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId) {
diff --git a/services/core/java/com/android/server/om/OverlayManagerSettings.java b/services/core/java/com/android/server/om/OverlayManagerSettings.java
index 7d00423..17b38de 100644
--- a/services/core/java/com/android/server/om/OverlayManagerSettings.java
+++ b/services/core/java/com/android/server/om/OverlayManagerSettings.java
@@ -104,7 +104,7 @@
         return true;
     }
 
-    OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
+    @NonNull OverlayInfo getOverlayInfo(@NonNull final String packageName, final int userId)
             throws BadKeyException {
         final int idx = select(packageName, userId);
         if (idx < 0) {
diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index cdc79c7..c04cdf6 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -35,6 +35,8 @@
 
 import dalvik.system.VMRuntime;
 
+import java.io.FileDescriptor;
+
 public class Installer extends SystemService {
     private static final String TAG = "Installer";
 
@@ -478,6 +480,16 @@
         }
     }
 
+    public void installApkVerity(String filePath, FileDescriptor verityInput)
+            throws InstallerException {
+        if (!checkBeforeRemote()) return;
+        try {
+            mInstalld.installApkVerity(filePath, verityInput);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     public boolean reconcileSecondaryDexFile(String apkPath, String packageName, int uid,
             String[] isas, @Nullable String volumeUuid, int flags) throws InstallerException {
         for (int i = 0; i < isas.length; i++) {
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 6a08e1b..324dc5f 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -111,11 +111,6 @@
             return false;
         }
 
-        // We do not dexopt a priv-app package when pm.dexopt.priv-apps-oob is true.
-        if (pkg.isPrivileged()) {
-            return !SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false);
-        }
-
         return true;
     }
 
@@ -481,6 +476,11 @@
             boolean isUsedByOtherApps) {
         int flags = info.flags;
         boolean vmSafeMode = (flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
+        // When pm.dexopt.priv-apps-oob is true, we only verify privileged apps.
+        if (info.isPrivilegedApp() &&
+            SystemProperties.getBoolean("pm.dexopt.priv-apps-oob", false)) {
+          return "verify";
+        }
         if (vmSafeMode) {
             return getSafeModeCompilerFilter(targetCompilerFilter);
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a73ad8d..5dfd3ae 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -52,11 +52,9 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_PACKAGE_CHANGED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE;
-import static android.content.pm.PackageManager.INSTALL_FAILED_SANDBOX_VERSION_DOWNGRADE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_USER_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_TEST_ONLY;
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
-import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
 import static android.content.pm.PackageManager.INSTALL_INTERNAL;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
@@ -314,6 +312,7 @@
 import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
 import com.android.server.pm.permission.PermissionsState;
 import com.android.server.pm.permission.PermissionsState.PermissionState;
+import com.android.server.security.VerityUtils;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 
 import dalvik.system.CloseGuard;
@@ -450,7 +449,6 @@
     static final int SCAN_NEW_INSTALL = 1<<2;
     static final int SCAN_UPDATE_TIME = 1<<3;
     static final int SCAN_BOOTING = 1<<4;
-    static final int SCAN_TRUSTED_OVERLAY = 1<<5;
     static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<6;
     static final int SCAN_REQUIRE_KNOWN = 1<<7;
     static final int SCAN_MOVE = 1<<8;
@@ -473,7 +471,6 @@
             SCAN_NEW_INSTALL,
             SCAN_UPDATE_TIME,
             SCAN_BOOTING,
-            SCAN_TRUSTED_OVERLAY,
             SCAN_DELETE_DATA_ON_FAILURES,
             SCAN_REQUIRE_KNOWN,
             SCAN_MOVE,
@@ -776,7 +773,7 @@
                 Collection<PackageParser.Package> allPackages, String targetPackageName) {
             List<PackageParser.Package> overlayPackages = null;
             for (PackageParser.Package p : allPackages) {
-                if (targetPackageName.equals(p.mOverlayTarget) && p.mIsStaticOverlay) {
+                if (targetPackageName.equals(p.mOverlayTarget) && p.mOverlayIsStatic) {
                     if (overlayPackages == null) {
                         overlayPackages = new ArrayList<PackageParser.Package>();
                     }
@@ -860,7 +857,7 @@
         void findStaticOverlayPackages() {
             synchronized (mPackages) {
                 for (PackageParser.Package p : mPackages.values()) {
-                    if (p.mIsStaticOverlay) {
+                    if (p.mOverlayIsStatic) {
                         if (mOverlayPackages == null) {
                             mOverlayPackages = new ArrayList<PackageParser.Package>();
                         }
@@ -2561,7 +2558,7 @@
                     | PackageParser.PARSE_IS_SYSTEM_DIR,
                     scanFlags
                     | SCAN_AS_SYSTEM
-                    | SCAN_TRUSTED_OVERLAY,
+                    | SCAN_AS_VENDOR,
                     0);
 
             mParallelPackageParserCallback.findStaticOverlayPackages();
@@ -10616,7 +10613,6 @@
                 }
             }
         }
-        pkg.mTrustedOverlay = (scanFlags & SCAN_TRUSTED_OVERLAY) != 0;
 
         if ((scanFlags & SCAN_AS_PRIVILEGED) != 0) {
             pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
@@ -10638,6 +10634,14 @@
         }
     }
 
+    private static @NonNull <T> T assertNotNull(@Nullable T object, String message)
+            throws PackageManagerException {
+        if (object == null) {
+            throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR, message);
+        }
+        return object;
+    }
+
     /**
      * Asserts the parsed package is valid according to the given policy. If the
      * package is invalid, for whatever reason, throws {@link PackageManagerException}.
@@ -10911,6 +10915,50 @@
                     }
                 }
             }
+
+            // Apply policies specific for runtime resource overlays (RROs).
+            if (pkg.mOverlayTarget != null) {
+                // System overlays have some restrictions on their use of the 'static' state.
+                if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
+                    // We are scanning a system overlay. This can be the first scan of the
+                    // system/vendor/oem partition, or an update to the system overlay.
+                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                        // This must be an update to a system overlay.
+                        final PackageSetting previousPkg = assertNotNull(
+                                mSettings.getPackageLPr(pkg.packageName),
+                                "previous package state not present");
+
+                        // Static overlays cannot be updated.
+                        if (previousPkg.pkg.mOverlayIsStatic) {
+                            throw new PackageManagerException("Overlay " + pkg.packageName +
+                                    " is static and cannot be upgraded.");
+                        // Non-static overlays cannot be converted to static overlays.
+                        } else if (pkg.mOverlayIsStatic) {
+                            throw new PackageManagerException("Overlay " + pkg.packageName +
+                                    " cannot be upgraded into a static overlay.");
+                        }
+                    }
+                } else {
+                    // The overlay is a non-system overlay. Non-system overlays cannot be static.
+                    if (pkg.mOverlayIsStatic) {
+                        throw new PackageManagerException("Overlay " + pkg.packageName +
+                                " is static but not pre-installed.");
+                    }
+
+                    // The only case where we allow installation of a non-system overlay is when
+                    // its signature is signed with the platform certificate.
+                    PackageSetting platformPkgSetting = mSettings.getPackageLPr("android");
+                    if ((platformPkgSetting.signatures.mSigningDetails
+                            != PackageParser.SigningDetails.UNKNOWN)
+                            && (compareSignatures(
+                                    platformPkgSetting.signatures.mSigningDetails.signatures,
+                                    pkg.mSigningDetails.signatures)
+                                            != PackageManager.SIGNATURE_MATCH)) {
+                        throw new PackageManagerException("Overlay " + pkg.packageName +
+                                " must be signed with the platform certificate.");
+                    }
+                }
+            }
         }
     }
 
@@ -16987,6 +17035,43 @@
             return;
         }
 
+        if (PackageManagerServiceUtils.isApkVerityEnabled()) {
+            String apkPath = null;
+            synchronized (mPackages) {
+                // Note that if the attacker managed to skip verify setup, for example by tampering
+                // with the package settings, upon reboot we will do full apk verification when
+                // verity is not detected.
+                final PackageSetting ps = mSettings.mPackages.get(pkgName);
+                if (ps != null && ps.isPrivileged()) {
+                    apkPath = pkg.baseCodePath;
+                }
+            }
+
+            if (apkPath != null) {
+                final VerityUtils.SetupResult result =
+                        VerityUtils.generateApkVeritySetupData(apkPath);
+                if (result.isOk()) {
+                    if (Build.IS_DEBUGGABLE) Slog.i(TAG, "Enabling apk verity to " + apkPath);
+                    FileDescriptor fd = result.getUnownedFileDescriptor();
+                    try {
+                        mInstaller.installApkVerity(apkPath, fd);
+                    } catch (InstallerException e) {
+                        res.setError(INSTALL_FAILED_INTERNAL_ERROR,
+                                "Failed to set up verity: " + e);
+                        return;
+                    } finally {
+                        IoUtils.closeQuietly(fd);
+                    }
+                } else if (result.isFailed()) {
+                    res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Failed to generate verity");
+                    return;
+                } else {
+                    // Do nothing if verity is skipped. Will fall back to full apk verification on
+                    // reboot.
+                }
+            }
+        }
+
         if (!instantApp) {
             startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
         } else {
@@ -17022,7 +17107,7 @@
         // Prepare the application profiles for the new code paths.
         // This needs to be done before invoking dexopt so that any install-time profile
         // can be used for optimizations.
-        mArtManagerService.prepareAppProfiles(pkg, args.user.getIdentifier());
+        mArtManagerService.prepareAppProfiles(pkg, resolveUserIds(args.user.getIdentifier()));
 
         // Check whether we need to dexopt the app.
         //
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 021c4b8..bf3eb8e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -579,7 +579,6 @@
         return true;
     }
 
-
     /**
      * Checks the signing certificates to see if the provided certificate is a member.  Invalid for
      * {@code SigningDetails} with multiple signing certificates.
@@ -642,10 +641,14 @@
         return false;
     }
 
+    /** Returns true if APK Verity is enabled. */
+    static boolean isApkVerityEnabled() {
+        return SystemProperties.getInt("ro.apk_verity.mode", 0) != 0;
+    }
+
     /** Returns true to force apk verification if the updated package (in /data) is a priv app. */
     static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) {
-        return disabledPs != null && disabledPs.isPrivileged() &&
-                SystemProperties.getInt("ro.apk_verity.mode", 0) != 0;
+        return disabledPs != null && disabledPs.isPrivileged() && isApkVerityEnabled();
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 92d159b..8178689 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -245,6 +245,14 @@
      */
     public void prepareAppProfiles(PackageParser.Package pkg, @UserIdInt int user) {
         final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
+        if (user < 0) {
+            Slog.wtf(TAG, "Invalid user id: " + user);
+            return;
+        }
+        if (appId < 0) {
+            Slog.wtf(TAG, "Invalid app id: " + appId);
+            return;
+        }
         try {
             ArrayMap<String, String> codePathsProfileNames = getPackageProfileNames(pkg);
             for (int i = codePathsProfileNames.size() - 1; i >= 0; i--) {
@@ -267,6 +275,15 @@
     }
 
     /**
+     * Prepares the app profiles for a set of users. {@see ArtManagerService#prepareAppProfiles}.
+     */
+    public void prepareAppProfiles(PackageParser.Package pkg, int[] user) {
+        for (int i = 0; i < user.length; i++) {
+            prepareAppProfiles(pkg, user[i]);
+        }
+    }
+
+    /**
      * Build the profiles names for all the package code paths (excluding resource only paths).
      * Return the map [code path -> profile name].
      */
diff --git a/services/core/java/com/android/server/security/VerityUtils.java b/services/core/java/com/android/server/security/VerityUtils.java
new file mode 100644
index 0000000..3908df4
--- /dev/null
+++ b/services/core/java/com/android/server/security/VerityUtils.java
@@ -0,0 +1,184 @@
+/*
+ * 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.server.security;
+
+import static android.system.OsConstants.PROT_READ;
+import static android.system.OsConstants.PROT_WRITE;
+
+import android.annotation.NonNull;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.apk.ApkSignatureVerifier;
+import android.util.apk.ByteBufferFactory;
+import android.util.apk.SignatureNotFoundException;
+import android.util.Slog;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.DigestException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+/** Provides fsverity related operations. */
+abstract public class VerityUtils {
+    private static final String TAG = "VerityUtils";
+
+    private static final boolean DEBUG = false;
+
+    /**
+     * Generates Merkle tree and fsverity metadata.
+     *
+     * @return {@code SetupResult} that contains the {@code EsetupResultCode}, and when success, the
+     *         {@code FileDescriptor} to read all the data from.
+     */
+    public static SetupResult generateApkVeritySetupData(@NonNull String apkPath) {
+        if (DEBUG) Slog.d(TAG, "Trying to install apk verity to " + apkPath);
+        SharedMemory shm = null;
+        try {
+            byte[] signedRootHash = ApkSignatureVerifier.getVerityRootHash(apkPath);
+            if (signedRootHash == null) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Skip verity tree generation since there is no root hash");
+                }
+                return SetupResult.skipped();
+            }
+
+            shm = generateApkVerityIntoSharedMemory(apkPath, signedRootHash);
+            FileDescriptor rfd = shm.getFileDescriptor();
+            if (rfd == null || !rfd.valid()) {
+                return SetupResult.failed();
+            }
+            return SetupResult.ok(Os.dup(rfd));
+        } catch (IOException | SecurityException | DigestException | NoSuchAlgorithmException |
+                SignatureNotFoundException | ErrnoException e) {
+            Slog.e(TAG, "Failed to set up apk verity: ", e);
+            return SetupResult.failed();
+        } finally {
+            if (shm != null) {
+                shm.close();
+            }
+        }
+    }
+
+    /**
+     * Returns a {@code SharedMemory} that contains Merkle tree and fsverity headers for the given
+     * apk, in the form that can immediately be used for fsverity setup.
+     */
+    private static SharedMemory generateApkVerityIntoSharedMemory(
+            String apkPath, byte[] expectedRootHash)
+            throws IOException, SecurityException, DigestException, NoSuchAlgorithmException,
+                   SignatureNotFoundException {
+        TrackedShmBufferFactory shmBufferFactory = new TrackedShmBufferFactory();
+        byte[] generatedRootHash = ApkSignatureVerifier.generateApkVerity(apkPath,
+                shmBufferFactory);
+        // We only generate Merkle tree once here, so it's important to make sure the root hash
+        // matches the signed one in the apk.
+        if (!Arrays.equals(expectedRootHash, generatedRootHash)) {
+            throw new SecurityException("Locally generated verity root hash does not match");
+        }
+
+        SharedMemory shm = shmBufferFactory.releaseSharedMemory();
+        if (shm == null) {
+            throw new IllegalStateException("Failed to generate verity tree into shared memory");
+        }
+        if (!shm.setProtect(PROT_READ)) {
+            throw new SecurityException("Failed to set up shared memory correctly");
+        }
+        return shm;
+    }
+
+    public static class SetupResult {
+        /** Result code if verity is set up correctly. */
+        private static final int RESULT_OK = 1;
+
+        /** Result code if the apk does not contain a verity root hash. */
+        private static final int RESULT_SKIPPED = 2;
+
+        /** Result code if the setup failed. */
+        private static final int RESULT_FAILED = 3;
+
+        private final int mCode;
+        private final FileDescriptor mFileDescriptor;
+
+        public static SetupResult ok(@NonNull FileDescriptor fileDescriptor) {
+            return new SetupResult(RESULT_OK, fileDescriptor);
+        }
+
+        public static SetupResult skipped() {
+            return new SetupResult(RESULT_SKIPPED, null);
+        }
+
+        public static SetupResult failed() {
+            return new SetupResult(RESULT_FAILED, null);
+        }
+
+        private SetupResult(int code, FileDescriptor fileDescriptor) {
+            this.mCode = code;
+            this.mFileDescriptor = fileDescriptor;
+        }
+
+        public boolean isFailed() {
+            return mCode == RESULT_FAILED;
+        }
+
+        public boolean isOk() {
+            return mCode == RESULT_OK;
+        }
+
+        public @NonNull FileDescriptor getUnownedFileDescriptor() {
+            return mFileDescriptor;
+        }
+    }
+
+    /** A {@code ByteBufferFactory} that creates a shared memory backed {@code ByteBuffer}. */
+    private static class TrackedShmBufferFactory implements ByteBufferFactory {
+        private SharedMemory mShm;
+        private ByteBuffer mBuffer;
+
+        @Override
+        public ByteBuffer create(int capacity) throws SecurityException {
+            try {
+                if (DEBUG) Slog.d(TAG, "Creating shared memory for apk verity");
+                // NB: This method is supposed to be called once according to the contract with
+                // ApkSignatureSchemeV2Verifier.
+                if (mBuffer != null) {
+                    throw new IllegalStateException("Multiple instantiation from this factory");
+                }
+                mShm = SharedMemory.create("apkverity", capacity);
+                if (!mShm.setProtect(PROT_READ | PROT_WRITE)) {
+                    throw new SecurityException("Failed to set protection");
+                }
+                mBuffer = mShm.mapReadWrite();
+                return mBuffer;
+            } catch (ErrnoException e) {
+                throw new SecurityException("Failed to set protection", e);
+            }
+        }
+
+        public SharedMemory releaseSharedMemory() {
+            if (mBuffer != null) {
+                SharedMemory.unmap(mBuffer);
+                mBuffer = null;
+            }
+            SharedMemory tmp = mShm;
+            mShm = null;
+            return tmp;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/stats/StatsCompanionService.java b/services/core/java/com/android/server/stats/StatsCompanionService.java
index f82dc24..baea964 100644
--- a/services/core/java/com/android/server/stats/StatsCompanionService.java
+++ b/services/core/java/com/android/server/stats/StatsCompanionService.java
@@ -18,16 +18,19 @@
 import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
+import android.app.StatsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.IntentSender;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
 import android.net.NetworkStats;
 import android.net.wifi.IWifiManager;
 import android.net.wifi.WifiActivityEnergyInfo;
+import android.os.StatsDimensionsValue;
 import android.os.BatteryStatsInternal;
 import android.os.Binder;
 import android.os.Bundle;
@@ -80,9 +83,12 @@
 
     static final String TAG = "StatsCompanionService";
     static final boolean DEBUG = true;
+
     public static final String ACTION_TRIGGER_COLLECTION =
         "com.android.server.stats.action.TRIGGER_COLLECTION";
 
+    public static final int CODE_SUBSCRIBER_BROADCAST = 1;
+
     private final Context mContext;
     private final AlarmManager mAlarmManager;
     @GuardedBy("sStatsdLock")
@@ -151,10 +157,37 @@
 
     @Override
     public void sendBroadcast(String pkg, String cls) {
+        // TODO: Use a pending intent, and enfoceCallingPermission.
         mContext.sendBroadcastAsUser(new Intent(ACTION_TRIGGER_COLLECTION).setClassName(pkg, cls),
                 UserHandle.SYSTEM);
     }
 
+    @Override
+    public void sendSubscriberBroadcast(IBinder intentSenderBinder, long configUid, long configKey,
+                                        long subscriptionId, long subscriptionRuleId,
+                                        StatsDimensionsValue dimensionsValue) {
+        if (DEBUG) Slog.d(TAG, "Statsd requested to sendSubscriberBroadcast.");
+        enforceCallingPermission();
+        IntentSender intentSender = new IntentSender(intentSenderBinder);
+        Intent intent = new Intent()
+                .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid)
+                .putExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, configKey)
+                .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, subscriptionId)
+                .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_RULE_ID, subscriptionRuleId)
+                .putExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, dimensionsValue);
+        try {
+            intentSender.sendIntent(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null);
+        } catch (IntentSender.SendIntentException e) {
+            Slog.w(TAG, "Unable to send using IntentSender from uid " + configUid
+                    + "; presumably it had been cancelled.");
+            if (DEBUG) {
+                Slog.d(TAG, String.format("SubscriberBroadcast params {%d %d %d %d %s}",
+                        configUid, configKey, subscriptionId,
+                        subscriptionRuleId, dimensionsValue));
+            }
+        }
+    }
+
     private final static int[] toIntArray(List<Integer> list) {
         int[] ret = new int[list.size()];
         for (int i = 0; i < ret.length; i++) {
diff --git a/services/core/java/com/android/server/utils/AppInstallerUtil.java b/services/core/java/com/android/server/utils/AppInstallerUtil.java
index af7ff41..5d2dbe6 100644
--- a/services/core/java/com/android/server/utils/AppInstallerUtil.java
+++ b/services/core/java/com/android/server/utils/AppInstallerUtil.java
@@ -56,6 +56,7 @@
         final Intent result = resolveIntent(context, intent);
         if (result != null) {
             result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+            result.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             return result;
         }
         return null;
diff --git a/services/net/java/android/net/util/MultinetworkPolicyTracker.java b/services/net/java/android/net/util/MultinetworkPolicyTracker.java
index 424e40d..30c5cd9 100644
--- a/services/net/java/android/net/util/MultinetworkPolicyTracker.java
+++ b/services/net/java/android/net/util/MultinetworkPolicyTracker.java
@@ -122,6 +122,7 @@
         return mAvoidBadWifi;
     }
 
+    // TODO: move this to MultipathPolicyTracker.
     public int getMeteredMultipathPreference() {
         return mMeteredMultipathPreference;
     }
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index ae311f8..d825533 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -13,9 +13,9 @@
 # limitations under the License.
 
 
-############################################################
-# FrameworksServicesLib app just for Robolectric test target.  #
-############################################################
+##############################################################
+# FrameworksServicesLib app just for Robolectric test target #
+##############################################################
 LOCAL_PATH := $(call my-dir)
 
 include $(CLEAR_VARS)
@@ -31,14 +31,43 @@
 
 include $(BUILD_PACKAGE)
 
-#############################################
-# FrameworksServices Robolectric test target. #
-#############################################
+##############################################
+# FrameworksServices Robolectric test target #
+##############################################
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+# Dependency platform-robolectric-android-all-stubs below contains a bunch of Android classes as
+# stubs that throw RuntimeExceptions when we use them. The goal is to include hidden APIs that
+# weren't included in Robolectric's Android jar files. However, we are testing the framework itself
+# here, so if we write stuff that is being used in the tests and exist in
+# platform-robolectric-android-all-stubs, the class loader is going to pick up the latter, and thus
+# we are going to test what we don't want. To solve this:
+#
+#   1. If the class being used should be visible to bundled apps:
+#      => Bypass the stubs target by including them in LOCAL_SRC_FILES and LOCAL_AIDL_INCLUDES
+#         (if aidl).
+#
+#   2. If it's not visible:
+#      => Remove the class from the stubs jar (common/robolectric/android-all/android-all-stubs.jar)
+#         and add the class path to
+#         common/robolectric/android-all/android-all-stubs_removed_classes.txt.
+#
 
-# Include the testing libraries (JUnit4 + Robolectric libs).
+INTERNAL_BACKUP := ../../core/java/com/android/internal/backup
+
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \
+    ../../core/java/android/content/pm/PackageInfo.java \
+    ../../core/java/android/app/backup/BackupAgent.java \
+    ../../core/java/android/app/backup/BackupDataOutput.java \
+    ../../core/java/android/app/backup/FullBackupDataOutput.java \
+    ../../core/java/android/app/IBackupAgent.aidl
+
+LOCAL_AIDL_INCLUDES := \
+    $(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \
+    ../../core/java/android/app/IBackupAgent.aidl
+
 LOCAL_STATIC_JAVA_LIBRARIES := \
     platform-robolectric-android-all-stubs \
     android-support-test \
@@ -58,9 +87,9 @@
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
-#############################################################
-# FrameworksServices runner target to run the previous target. #
-#############################################################
+###############################################################
+# FrameworksServices runner target to run the previous target #
+###############################################################
 include $(CLEAR_VARS)
 
 LOCAL_MODULE := RunFrameworksServicesRoboTests
diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
new file mode 100644
index 0000000..3668350
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
@@ -0,0 +1,315 @@
+/*
+ * 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.server.backup;
+
+import static com.android.server.backup.testing.TransportData.backupTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransport;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import static java.util.Collections.emptyList;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toList;
+
+import android.app.Application;
+import android.app.IActivityManager;
+import android.app.IBackupAgent;
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.FullBackupDataOutput;
+import android.app.backup.IBackupManager;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IBackupObserver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.internal.BackupHandler;
+import com.android.server.backup.internal.BackupRequest;
+import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.internal.PerformBackupTask;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderClasses;
+import com.android.server.testing.shadows.FrameworkShadowPackageManager;
+import com.android.server.testing.shadows.ShadowBackupDataInput;
+import com.android.server.testing.shadows.ShadowBackupDataOutput;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.shadows.ShadowQueuedWork;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(
+    manifest = Config.NONE,
+    sdk = 26,
+    shadows = {
+        FrameworkShadowPackageManager.class,
+        ShadowBackupDataInput.class,
+        ShadowBackupDataOutput.class,
+        ShadowQueuedWork.class
+    }
+)
+@SystemLoaderClasses({
+    PerformBackupTask.class,
+    BackupDataOutput.class,
+    FullBackupDataOutput.class,
+    TransportManager.class,
+    BackupAgent.class,
+    IBackupTransport.class,
+    IBackupAgent.class,
+    PackageInfo.class
+})
+@Presubmit
+public class PerformBackupTaskTest {
+    private static final String PACKAGE_1 = "com.example.package1";
+    private static final String PACKAGE_2 = "com.example.package2";
+
+    @Mock private RefactoredBackupManagerService mBackupManagerService;
+    @Mock private TransportManager mTransportManager;
+    @Mock private DataChangedJournal mDataChangedJournal;
+    @Mock private IBackupObserver mObserver;
+    @Mock private IBackupManagerMonitor mMonitor;
+    @Mock private OnTaskFinishedListener mListener;
+    private TransportData mTransport;
+    private IBackupTransport mTransportBinder;
+    private TransportClient mTransportClient;
+    private ShadowLooper mShadowBackupLooper;
+    private BackupHandler mBackupHandler;
+    private PowerManager.WakeLock mWakeLock;
+    private ShadowPackageManager mShadowPackageManager;
+    private FakeIBackupManager mBackupManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mTransport = backupTransport();
+        TransportMock transportMock = setUpTransport(mTransportManager, mTransport);
+        mTransportBinder = transportMock.transport;
+        mTransportClient = transportMock.transportClient;
+
+        Application application = RuntimeEnvironment.application;
+        File cacheDir = application.getCacheDir();
+        File baseStateDir = new File(cacheDir, "base_state_dir");
+        File dataDir = new File(cacheDir, "data_dir");
+        File stateDir = new File(baseStateDir, mTransport.transportDirName);
+        assertThat(baseStateDir.mkdir()).isTrue();
+        assertThat(dataDir.mkdir()).isTrue();
+        assertThat(stateDir.mkdir()).isTrue();
+
+        PackageManager packageManager = application.getPackageManager();
+        mShadowPackageManager = Shadow.extract(packageManager);
+
+        PowerManager powerManager =
+                (PowerManager) application.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
+
+        // Robolectric simulates multi-thread in a single-thread to avoid flakiness
+        HandlerThread backupThread = new HandlerThread("backup");
+        backupThread.setUncaughtExceptionHandler(
+                (t, e) -> fail("Uncaught exception " + e.getMessage()));
+        backupThread.start();
+        Looper backupLooper = backupThread.getLooper();
+        mShadowBackupLooper = shadowOf(backupLooper);
+        mBackupHandler = new BackupHandler(mBackupManagerService, backupLooper);
+
+        mBackupManager = spy(FakeIBackupManager.class);
+
+        when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager);
+        when(mBackupManagerService.getContext()).thenReturn(application);
+        when(mBackupManagerService.getPackageManager()).thenReturn(packageManager);
+        when(mBackupManagerService.getWakelock()).thenReturn(mWakeLock);
+        when(mBackupManagerService.getCurrentOpLock()).thenReturn(new Object());
+        when(mBackupManagerService.getQueueLock()).thenReturn(new Object());
+        when(mBackupManagerService.getBaseStateDir()).thenReturn(baseStateDir);
+        when(mBackupManagerService.getDataDir()).thenReturn(dataDir);
+        when(mBackupManagerService.getCurrentOperations()).thenReturn(new SparseArray<>());
+        when(mBackupManagerService.getBackupHandler()).thenReturn(mBackupHandler);
+        when(mBackupManagerService.getBackupManagerBinder()).thenReturn(mBackupManager);
+        when(mBackupManagerService.getActivityManager()).thenReturn(mock(IActivityManager.class));
+    }
+
+    @Test
+    public void testRunTask_whenTransportProvidesFlags_passesThemToTheAgent() throws Exception {
+        BackupAgent agent = setUpAgent(PACKAGE_1);
+        int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+        when(mTransportBinder.getTransportFlags()).thenReturn(flags);
+        PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+
+        runTask(task);
+
+        verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+    }
+
+    @Test
+    public void testRunTask_whenTransportDoesNotProvidesFlags() throws Exception {
+        BackupAgent agent = setUpAgent(PACKAGE_1);
+        PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+
+        runTask(task);
+
+        verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(0)), any());
+    }
+
+    @Test
+    public void testRunTask_whenTransportProvidesFlagsAndMultipleAgents_passesToAll()
+            throws Exception {
+        List<BackupAgent> agents = setUpAgents(PACKAGE_1, PACKAGE_2);
+        BackupAgent agent1 = agents.get(0);
+        BackupAgent agent2 = agents.get(1);
+        int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+        when(mTransportBinder.getTransportFlags()).thenReturn(flags);
+        PerformBackupTask task =
+                createPerformBackupTask(emptyList(), false, true, PACKAGE_1, PACKAGE_2);
+
+        runTask(task);
+
+        verify(agent1).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+        verify(agent2).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+    }
+
+    @Test
+    public void testRunTask_whenTransportChangeFlagsAfterTaskCreation() throws Exception {
+        BackupAgent agent = setUpAgent(PACKAGE_1);
+        PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+        int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+        when(mTransportBinder.getTransportFlags()).thenReturn(flags);
+
+        runTask(task);
+
+        verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+    }
+
+    private void runTask(PerformBackupTask task) {
+        Message message = mBackupHandler.obtainMessage(BackupHandler.MSG_BACKUP_RESTORE_STEP, task);
+        mBackupHandler.sendMessage(message);
+        while (mShadowBackupLooper.getScheduler().areAnyRunnable()) {
+            mShadowBackupLooper.runToEndOfTasks();
+        }
+    }
+
+    private List<BackupAgent> setUpAgents(String... packageNames) {
+        return Stream.of(packageNames).map(this::setUpAgent).collect(toList());
+    }
+
+    private BackupAgent setUpAgent(String packageName) {
+        PackageInfo packageInfo = new PackageInfo();
+        packageInfo.packageName = packageName;
+        packageInfo.applicationInfo = new ApplicationInfo();
+        packageInfo.applicationInfo.flags = ApplicationInfo.FLAG_ALLOW_BACKUP;
+        packageInfo.applicationInfo.backupAgentName = "BackupAgent" + packageName;
+        packageInfo.applicationInfo.packageName = packageName;
+        mShadowPackageManager.setApplicationEnabledSetting(
+                packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+        mShadowPackageManager.addPackage(packageInfo);
+        BackupAgent backupAgent = spy(BackupAgent.class);
+        IBackupAgent backupAgentBinder = IBackupAgent.Stub.asInterface(backupAgent.onBind());
+        when(mBackupManagerService.bindToAgentSynchronous(
+                        eq(packageInfo.applicationInfo), anyInt()))
+                .thenReturn(backupAgentBinder);
+        return backupAgent;
+    }
+
+    private PerformBackupTask createPerformBackupTask(
+            List<String> pendingFullBackups,
+            boolean userInitiated,
+            boolean nonIncremental,
+            String... packages) {
+        ArrayList<BackupRequest> backupRequests =
+                Stream.of(packages).map(BackupRequest::new).collect(toCollection(ArrayList::new));
+        mWakeLock.acquire();
+        PerformBackupTask task =
+                new PerformBackupTask(
+                        mBackupManagerService,
+                        mTransportClient,
+                        mTransport.transportDirName,
+                        backupRequests,
+                        mDataChangedJournal,
+                        mObserver,
+                        mMonitor,
+                        mListener,
+                        pendingFullBackups,
+                        userInitiated,
+                        nonIncremental);
+        mBackupManager.setUp(mBackupHandler, task);
+        return task;
+    }
+
+    private ArgumentMatcher<BackupDataOutput> dataOutputWithTransportFlags(int flags) {
+        return dataOutput -> dataOutput.getTransportFlags() == flags;
+    }
+
+    private abstract static class FakeIBackupManager extends IBackupManager.Stub {
+        private Handler mBackupHandler;
+        private BackupRestoreTask mTask;
+
+        public FakeIBackupManager() {}
+
+        private void setUp(Handler backupHandler, BackupRestoreTask task) {
+            mBackupHandler = backupHandler;
+            mTask = task;
+        }
+
+        @Override
+        public void opComplete(int token, long result) throws RemoteException {
+            assertThat(mTask).isNotNull();
+            Message message =
+                    mBackupHandler.obtainMessage(
+                            BackupHandler.MSG_OP_COMPLETE, Pair.create(mTask, result));
+            mBackupHandler.sendMessage(message);
+        }
+    }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/src/com/android/server/backup/testing/TestUtils.java
index 1be298d..407a1bc 100644
--- a/services/robotests/src/com/android/server/backup/testing/TestUtils.java
+++ b/services/robotests/src/com/android/server/backup/testing/TestUtils.java
@@ -16,11 +16,28 @@
 
 package com.android.server.backup.testing;
 
+import static com.google.common.truth.Truth.assertThat;
+
+
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 
+import org.robolectric.shadows.ShadowLog;
+
 import java.util.concurrent.Callable;
 
 public class TestUtils {
+    /** Reset logcat with {@link ShadowLog#reset()} before the test case */
+    public static void assertLogcatAtMost(String tag, int level) {
+        assertThat(ShadowLog.getLogsForTag(tag).stream().allMatch(logItem -> logItem.type <= level))
+                .isTrue();
+    }
+
+    /** Reset logcat with {@link ShadowLog#reset()} before the test case */
+    public static void assertLogcatAtLeast(String tag, int level) {
+        assertThat(ShadowLog.getLogsForTag(tag).stream().anyMatch(logItem -> logItem.type >= level))
+                .isTrue();
+    }
+
     /**
      * Calls {@link Runnable#run()} and returns if no exception is thrown. Otherwise, if the
      * exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} and
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
index e1dc7b5..565c7e6 100644
--- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
+++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
@@ -144,12 +144,14 @@
                 // Transport registered and available
                 IBackupTransport transportMock = mockTransportBinder(transport);
                 when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
+                when(transportClientMock.connect(any())).thenReturn(transportMock);
 
                 return new TransportMock(transportClientMock, transportMock);
             } else {
                 // Transport registered but unavailable
                 when(transportClientMock.connectOrThrow(any()))
                         .thenThrow(TransportNotAvailableException.class);
+                when(transportClientMock.connect(any())).thenReturn(null);
 
                 return new TransportMock(transportClientMock, null);
             }
diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
index 10442b7..db6e62f 100644
--- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
+++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
@@ -17,7 +17,11 @@
 package com.android.server.backup.transport;
 
 import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+import static com.android.server.backup.testing.TestUtils.assertLogcatAtLeast;
+import static com.android.server.backup.testing.TestUtils.assertLogcatAtMost;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -25,6 +29,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.robolectric.Shadows.shadowOf;
+import static org.testng.Assert.expectThrows;
 
 import android.content.ComponentName;
 import android.content.Context;
@@ -34,12 +39,17 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
+import android.util.Log;
+
 import com.android.internal.backup.IBackupTransport;
 import com.android.server.EventLogTags;
 import com.android.server.backup.TransportManager;
 import com.android.server.testing.FrameworkRobolectricTestRunner;
-import com.android.server.testing.ShadowEventLog;
 import com.android.server.testing.SystemLoaderClasses;
+import com.android.server.testing.shadows.ShadowCloseGuard;
+import com.android.server.testing.shadows.ShadowEventLog;
+import com.android.server.testing.shadows.ShadowSlog;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -47,10 +57,15 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLog;
 import org.robolectric.shadows.ShadowLooper;
 
 @RunWith(FrameworkRobolectricTestRunner.class)
-@Config(manifest = Config.NONE, sdk = 26, shadows = {ShadowEventLog.class})
+@Config(
+    manifest = Config.NONE,
+    sdk = 26,
+    shadows = {ShadowEventLog.class, ShadowCloseGuard.class, ShadowSlog.class}
+)
 @SystemLoaderClasses({TransportManager.class, TransportClient.class})
 @Presubmit
 public class TransportClientTest {
@@ -59,7 +74,7 @@
     @Mock private Context mContext;
     @Mock private TransportConnectionListener mTransportConnectionListener;
     @Mock private TransportConnectionListener mTransportConnectionListener2;
-    @Mock private IBackupTransport.Stub mIBackupTransport;
+    @Mock private IBackupTransport.Stub mTransportBinder;
     private TransportClient mTransportClient;
     private ComponentName mTransportComponent;
     private String mTransportString;
@@ -78,7 +93,12 @@
         mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent);
         mTransportClient =
                 new TransportClient(
-                        mContext, mBindIntent, mTransportComponent, "1", new Handler(mainLooper));
+                        mContext,
+                        mBindIntent,
+                        mTransportComponent,
+                        "1",
+                        "caller",
+                        new Handler(mainLooper));
 
         when(mContext.bindServiceAsUser(
                         eq(mBindIntent),
@@ -111,7 +131,7 @@
 
         // Simulate framework connecting
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
-        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
 
         mShadowLooper.runToEndOfTasks();
         verify(mTransportConnectionListener)
@@ -126,7 +146,7 @@
 
         mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
 
-        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
 
         mShadowLooper.runToEndOfTasks();
         verify(mTransportConnectionListener)
@@ -139,7 +159,7 @@
     public void testConnectAsync_whenAlreadyConnected_callsListener() throws Exception {
         mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
-        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
 
         mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
 
@@ -180,11 +200,11 @@
     }
 
     @Test
-    public void testConnectAsync_afterServiceDisconnectedBeforeNewConnection_callsListener()
+    public void testConnectAsync_afterOnServiceDisconnectedBeforeNewConnection_callsListener()
             throws Exception {
         mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
-        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
         connection.onServiceDisconnected(mTransportComponent);
 
         mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
@@ -194,13 +214,13 @@
     }
 
     @Test
-    public void testConnectAsync_afterServiceDisconnectedAfterNewConnection_callsListener()
+    public void testConnectAsync_afterOnServiceDisconnectedAfterNewConnection_callsListener()
             throws Exception {
         mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
-        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
         connection.onServiceDisconnected(mTransportComponent);
-        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
 
         mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
 
@@ -240,7 +260,7 @@
 
     @Test
     public void testConnectAsync_beforeFrameworkCall_logsBoundTransition() {
-        ShadowEventLog.clearEvents();
+        ShadowEventLog.setUp();
 
         mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
 
@@ -249,11 +269,11 @@
 
     @Test
     public void testConnectAsync_afterOnServiceConnected_logsBoundAndConnectedTransitions() {
-        ShadowEventLog.clearEvents();
+        ShadowEventLog.setUp();
 
         mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
-        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
 
         assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 1);
         assertEventLogged(EventLogTags.BACKUP_TRANSPORT_CONNECTION, mTransportString, 1);
@@ -261,7 +281,7 @@
 
     @Test
     public void testConnectAsync_afterOnBindingDied_logsBoundAndUnboundTransitions() {
-        ShadowEventLog.clearEvents();
+        ShadowEventLog.setUp();
 
         mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
@@ -275,8 +295,8 @@
     public void testUnbind_whenConnected_logsDisconnectedAndUnboundTransitions() {
         mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
-        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
-        ShadowEventLog.clearEvents();
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
+        ShadowEventLog.setUp();
 
         mTransportClient.unbind("caller1");
 
@@ -288,8 +308,8 @@
     public void testOnServiceDisconnected_whenConnected_logsDisconnectedAndUnboundTransitions() {
         mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
-        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
-        ShadowEventLog.clearEvents();
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
+        ShadowEventLog.setUp();
 
         connection.onServiceDisconnected(mTransportComponent);
 
@@ -301,8 +321,8 @@
     public void testOnBindingDied_whenConnected_logsDisconnectedAndUnboundTransitions() {
         mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
         ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
-        connection.onServiceConnected(mTransportComponent, mIBackupTransport);
-        ShadowEventLog.clearEvents();
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
+        ShadowEventLog.setUp();
 
         connection.onBindingDied(mTransportComponent);
 
@@ -310,6 +330,122 @@
         assertEventLogged(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, mTransportString, 0);
     }
 
+    @Test
+    public void testMarkAsDisposed_whenCreated() throws Throwable {
+        mTransportClient.markAsDisposed();
+
+        // No exception thrown
+    }
+
+    @Test
+    public void testMarkAsDisposed_afterOnBindingDied() throws Throwable {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onBindingDied(mTransportComponent);
+
+        mTransportClient.markAsDisposed();
+
+        // No exception thrown
+    }
+
+    @Test
+    public void testMarkAsDisposed_whenConnectedAndUnbound() throws Throwable {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
+        mTransportClient.unbind("caller1");
+
+        mTransportClient.markAsDisposed();
+
+        // No exception thrown
+    }
+
+    @Test
+    public void testMarkAsDisposed_afterOnServiceDisconnected() throws Throwable {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
+        connection.onServiceDisconnected(mTransportComponent);
+
+        mTransportClient.markAsDisposed();
+
+        // No exception thrown
+    }
+
+    @Test
+    public void testMarkAsDisposed_whenBound() throws Throwable {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+
+        expectThrows(RuntimeException.class, mTransportClient::markAsDisposed);
+    }
+
+    @Test
+    public void testMarkAsDisposed_whenConnected() throws Throwable {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
+
+        expectThrows(RuntimeException.class, mTransportClient::markAsDisposed);
+    }
+
+    @Test
+    @SuppressWarnings("FinalizeCalledExplicitly")
+    public void testFinalize_afterCreated() throws Throwable {
+        ShadowLog.reset();
+
+        mTransportClient.finalize();
+
+        assertLogcatAtMost(TransportClient.TAG, Log.INFO);
+    }
+
+    @Test
+    @SuppressWarnings("FinalizeCalledExplicitly")
+    public void testFinalize_whenBound() throws Throwable {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ShadowLog.reset();
+
+        mTransportClient.finalize();
+
+        assertLogcatAtLeast(TransportClient.TAG, Log.ERROR);
+    }
+
+    @Test
+    @SuppressWarnings("FinalizeCalledExplicitly")
+    public void testFinalize_whenConnected() throws Throwable {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(mTransportComponent, mTransportBinder);
+        ShadowLog.reset();
+
+        mTransportClient.finalize();
+
+        expectThrows(
+                TransportNotAvailableException.class,
+                () -> mTransportClient.getConnectedTransport("caller1"));
+        assertLogcatAtLeast(TransportClient.TAG, Log.ERROR);
+    }
+
+    @Test
+    @SuppressWarnings("FinalizeCalledExplicitly")
+    public void testFinalize_whenNotMarkedAsDisposed() throws Throwable {
+        ShadowCloseGuard.setUp();
+
+        mTransportClient.finalize();
+
+        assertThat(ShadowCloseGuard.hasReported()).isTrue();
+    }
+
+    @Test
+    @SuppressWarnings("FinalizeCalledExplicitly")
+    public void testFinalize_whenMarkedAsDisposed() throws Throwable {
+        mTransportClient.markAsDisposed();
+        ShadowCloseGuard.setUp();
+
+        mTransportClient.finalize();
+
+        assertThat(ShadowCloseGuard.hasReported()).isFalse();
+    }
+
     private void assertEventLogged(int tag, Object... values) {
         assertThat(ShadowEventLog.hasEvent(tag, values)).isTrue();
     }
diff --git a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
index 6c7313b..c94d598 100644
--- a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
+++ b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
@@ -16,6 +16,9 @@
 
 package com.android.server.testing;
 
+import com.android.server.backup.PerformBackupTaskTest;
+import com.android.server.backup.internal.PerformBackupTask;
+
 import com.google.common.collect.ImmutableSet;
 
 import org.junit.runners.model.FrameworkMethod;
@@ -30,6 +33,9 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Enumeration;
 import java.util.Set;
 
 import javax.annotation.Nonnull;
@@ -115,6 +121,27 @@
         }
 
         /**
+         * HACK^2
+         * The framework Robolectric run configuration puts a prebuilt in front of us, so we try not
+         * to load the class from there, if possible.
+         */
+        @Override
+        public InputStream getResourceAsStream(String resource) {
+            try {
+                Enumeration<URL> urls = getResources(resource);
+                while (urls.hasMoreElements()) {
+                    URL url = urls.nextElement();
+                    if (!url.toString().toLowerCase().contains("prebuilt")) {
+                        return url.openStream();
+                    }
+                }
+            } catch (IOException e) {
+                // Fall through
+            }
+            return super.getResourceAsStream(resource);
+        }
+
+        /**
          * Classes like com.package.ClassName$InnerClass should also be loaded from the system class
          * loader, so we test if the classes in the annotation are prefixes of the class to load.
          */
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java
new file mode 100644
index 0000000..28489af
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java
@@ -0,0 +1,61 @@
+/*
+ * 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.server.testing.shadows;
+
+import android.app.backup.BackupDataInput;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+@Implements(BackupDataInput.class)
+public class ShadowBackupDataInput {
+    @Implementation
+    public void __constructor__(FileDescriptor fd) {
+    }
+
+    @Implementation
+    protected void finalize() throws Throwable {
+    }
+
+    @Implementation
+    public boolean readNextHeader() throws IOException {
+        return false;
+    }
+
+    @Implementation
+    public String getKey() {
+        throw new AssertionError("Can't call because readNextHeader() returned false");
+    }
+
+    @Implementation
+    public int getDataSize() {
+        throw new AssertionError("Can't call because readNextHeader() returned false");
+    }
+
+    @Implementation
+    public int readEntityData(byte[] data, int offset, int size) throws IOException {
+        throw new AssertionError("Can't call because readNextHeader() returned false");
+    }
+
+    @Implementation
+    public void skipEntityData() throws IOException {
+        throw new AssertionError("Can't call because readNextHeader() returned false");
+    }
+}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java
new file mode 100644
index 0000000..c7deada
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java
@@ -0,0 +1,57 @@
+/*
+ * 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.server.testing.shadows;
+
+import android.app.backup.BackupDataOutput;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+@Implements(BackupDataOutput.class)
+public class ShadowBackupDataOutput {
+    private long mQuota;
+    private int mTransportFlags;
+
+    @Implementation
+    public void __constructor__(FileDescriptor fd, long quota, int transportFlags) {
+        mQuota = quota;
+        mTransportFlags = transportFlags;
+    }
+
+    @Implementation
+    public long getQuota() {
+        return mQuota;
+    }
+
+    @Implementation
+    public int getTransportFlags() {
+        return mTransportFlags;
+    }
+
+    @Implementation
+    public int writeEntityHeader(String key, int dataSize) throws IOException {
+        return 0;
+    }
+
+    @Implementation
+    public int writeEntityData(byte[] data, int size) throws IOException {
+        return 0;
+    }
+}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowCloseGuard.java b/services/robotests/src/com/android/server/testing/shadows/ShadowCloseGuard.java
new file mode 100644
index 0000000..c9984bf
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowCloseGuard.java
@@ -0,0 +1,48 @@
+/*
+ * 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.server.testing.shadows;
+
+import dalvik.system.CloseGuard;
+
+import org.robolectric.annotation.Implements;
+
+@Implements(CloseGuard.class)
+public class ShadowCloseGuard {
+    private static final Reporter REPORTER = new Reporter();
+
+    public static boolean hasReported() {
+        return REPORTER.mReports > 0;
+    }
+
+    public static void setUp() {
+        // Can't do this in static {} block because shadow initialization is part of real class
+        // initialization and it happens right in the beginning. When the shadow is being
+        // initialized the class hasn't been initialized yet and it will be after the shadow. So,
+        // REPORTER field (inside CloseGuard) will be assigned *after* setReporter() is called.
+        CloseGuard.setReporter(REPORTER);
+        REPORTER.mReports = 0;
+    }
+
+    private static class Reporter implements CloseGuard.Reporter {
+        private int mReports = 0;
+
+        @Override
+        public void report(String message, Throwable allocationSite) {
+            mReports += 1;
+        }
+    }
+}
diff --git a/services/robotests/src/com/android/server/testing/ShadowEventLog.java b/services/robotests/src/com/android/server/testing/shadows/ShadowEventLog.java
similarity index 94%
rename from services/robotests/src/com/android/server/testing/ShadowEventLog.java
rename to services/robotests/src/com/android/server/testing/shadows/ShadowEventLog.java
index b8059f4..4625684 100644
--- a/services/robotests/src/com/android/server/testing/ShadowEventLog.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowEventLog.java
@@ -14,7 +14,7 @@
  * limitations under the License
  */
 
-package com.android.server.testing;
+package com.android.server.testing.shadows;
 
 import android.util.EventLog;
 
@@ -40,7 +40,8 @@
         return ENTRIES.contains(new Entry(tag, Arrays.asList(values)));
     }
 
-    public static void clearEvents() {
+    /** Clears the entries */
+    public static void setUp() {
         ENTRIES.clear();
     }
 
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowSlog.java b/services/robotests/src/com/android/server/testing/shadows/ShadowSlog.java
new file mode 100644
index 0000000..bf4b61e
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowSlog.java
@@ -0,0 +1,32 @@
+/*
+ * 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.server.testing.shadows;
+
+import android.util.Log;
+import android.util.Slog;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowLog;
+
+@Implements(Slog.class)
+public class ShadowSlog {
+    @Implementation
+    public static int println(int priority, String tag, String msg) {
+        return Log.println(priority, tag, msg);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
index a29e169..b68bf2d 100644
--- a/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
@@ -266,7 +266,7 @@
         assertEquals(((restrictionTypes & JOBS_ONLY) != 0),
                 instance.areJobsRestricted(uid, packageName, exemptFromBatterySaver));
         assertEquals(((restrictionTypes & ALARMS_ONLY) != 0),
-                instance.areAlarmsRestricted(uid, packageName));
+                instance.areAlarmsRestricted(uid, packageName, exemptFromBatterySaver));
     }
 
     private void areRestricted(ForceAppStandbyTrackerTestable instance, int uid, String packageName,
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 5c7348c..c491601 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -195,7 +195,6 @@
         assertEquals(a.installLocation, b.installLocation);
         assertEquals(a.coreApp, b.coreApp);
         assertEquals(a.mRequiredForAllUsers, b.mRequiredForAllUsers);
-        assertEquals(a.mTrustedOverlay, b.mTrustedOverlay);
         assertEquals(a.mCompileSdkVersion, b.mCompileSdkVersion);
         assertEquals(a.mCompileSdkVersionCodename, b.mCompileSdkVersionCodename);
         assertEquals(a.use32bitAbi, b.use32bitAbi);
@@ -429,7 +428,6 @@
         pkg.installLocation = 100;
         pkg.coreApp = true;
         pkg.mRequiredForAllUsers = true;
-        pkg.mTrustedOverlay = true;
         pkg.use32bitAbi = true;
         pkg.packageName = "foo";
         pkg.splitNames = new String[] { "foo2" };
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index d17bdc8..6799417 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -1960,6 +1960,15 @@
         }
     }
 
+    /** {@hide} */
+    final void internalOnHandoverComplete() {
+        for (CallbackRecord<Callback> record : mCallbackRecords) {
+            final Call call = this;
+            final Callback callback = record.getCallback();
+            record.getHandler().post(() -> callback.onHandoverComplete(call));
+        }
+    }
+
     private void fireStateChanged(final int newState) {
         for (CallbackRecord<Callback> record : mCallbackRecords) {
             final Call call = this;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 7522443..63f970a 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -2801,6 +2801,15 @@
     public void onCallEvent(String event, Bundle extras) {}
 
     /**
+     * Notifies this {@link Connection} that a handover has completed.
+     * <p>
+     * A handover is initiated with {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int,
+     * Bundle)} on the initiating side of the handover, and
+     * {@link TelecomManager#acceptHandover(Uri, int, PhoneAccountHandle)}.
+     */
+    public void onHandoverComplete() {}
+
+    /**
      * Notifies this {@link Connection} of a change to the extras made outside the
      * {@link ConnectionService}.
      * <p>
diff --git a/telecomm/java/android/telecom/ConnectionService.java b/telecomm/java/android/telecom/ConnectionService.java
index 6af01ae..c1040ad 100644
--- a/telecomm/java/android/telecom/ConnectionService.java
+++ b/telecomm/java/android/telecom/ConnectionService.java
@@ -140,6 +140,7 @@
     private static final String SESSION_POST_DIAL_CONT = "CS.oPDC";
     private static final String SESSION_PULL_EXTERNAL_CALL = "CS.pEC";
     private static final String SESSION_SEND_CALL_EVENT = "CS.sCE";
+    private static final String SESSION_HANDOVER_COMPLETE = "CS.hC";
     private static final String SESSION_EXTRAS_CHANGED = "CS.oEC";
     private static final String SESSION_START_RTT = "CS.+RTT";
     private static final String SESSION_STOP_RTT = "CS.-RTT";
@@ -179,6 +180,7 @@
     private static final int MSG_CONNECTION_SERVICE_FOCUS_LOST = 30;
     private static final int MSG_CONNECTION_SERVICE_FOCUS_GAINED = 31;
     private static final int MSG_HANDOVER_FAILED = 32;
+    private static final int MSG_HANDOVER_COMPLETE = 33;
 
     private static Connection sNullConnection;
 
@@ -298,6 +300,19 @@
         }
 
         @Override
+        public void handoverComplete(String callId, Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, SESSION_HANDOVER_COMPLETE);
+            try {
+                SomeArgs args = SomeArgs.obtain();
+                args.arg1 = callId;
+                args.arg2 = Log.createSubsession();
+                mHandler.obtainMessage(MSG_HANDOVER_COMPLETE, args).sendToTarget();
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void abort(String callId, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, SESSION_ABORT);
             try {
@@ -1028,6 +1043,19 @@
                     }
                     break;
                 }
+                case MSG_HANDOVER_COMPLETE: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Log.continueSession((Session) args.arg2,
+                                SESSION_HANDLER + SESSION_HANDOVER_COMPLETE);
+                        String callId = (String) args.arg1;
+                        notifyHandoverComplete(callId);
+                    } finally {
+                        args.recycle();
+                        Log.endSession();
+                    }
+                    break;
+                }
                 case MSG_ON_EXTRAS_CHANGED: {
                     SomeArgs args = (SomeArgs) msg.obj;
                     try {
@@ -1445,19 +1473,24 @@
             final ConnectionRequest request,
             boolean isIncoming,
             boolean isUnknown) {
+        boolean isLegacyHandover = request.getExtras() != null &&
+                request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER, false);
+        boolean isHandover = request.getExtras() != null && request.getExtras().getBoolean(
+                TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, false);
         Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
-                        "isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
-                isIncoming,
-                isUnknown);
+                        "isIncoming: %b, isUnknown: %b, isLegacyHandover: %b, isHandover: %b",
+                callManagerAccount, callId, request, isIncoming, isUnknown, isLegacyHandover,
+                isHandover);
 
         Connection connection = null;
-        if (getApplicationContext().getApplicationInfo().targetSdkVersion >
-                Build.VERSION_CODES.O_MR1 && request.getExtras() != null &&
-                request.getExtras().getBoolean(TelecomManager.EXTRA_IS_HANDOVER,false)) {
+        if (isHandover) {
+            PhoneAccountHandle fromPhoneAccountHandle = request.getExtras() != null
+                    ? (PhoneAccountHandle) request.getExtras().getParcelable(
+                    TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT) : null;
             if (!isIncoming) {
-                connection = onCreateOutgoingHandoverConnection(callManagerAccount, request);
+                connection = onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request);
             } else {
-                connection = onCreateIncomingHandoverConnection(callManagerAccount, request);
+                connection = onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request);
             }
         } else {
             connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
@@ -1754,6 +1787,19 @@
     }
 
     /**
+     * Notifies a {@link Connection} that a handover has completed.
+     *
+     * @param callId The ID of the call which completed handover.
+     */
+    private void notifyHandoverComplete(String callId) {
+        Log.d(this, "notifyHandoverComplete(%s)", callId);
+        Connection connection = findConnectionForAction(callId, "notifyHandoverComplete");
+        if (connection != null) {
+            connection.onHandoverComplete();
+        }
+    }
+
+    /**
      * Notifies a {@link Connection} or {@link Conference} of a change to the extras from Telecom.
      * <p>
      * These extra changes can originate from Telecom itself, or from an {@link InCallService} via
diff --git a/telecomm/java/android/telecom/InCallService.java b/telecomm/java/android/telecom/InCallService.java
index 74fa62d..fcf04c9 100644
--- a/telecomm/java/android/telecom/InCallService.java
+++ b/telecomm/java/android/telecom/InCallService.java
@@ -81,6 +81,7 @@
     private static final int MSG_ON_RTT_UPGRADE_REQUEST = 10;
     private static final int MSG_ON_RTT_INITIATION_FAILURE = 11;
     private static final int MSG_ON_HANDOVER_FAILED = 12;
+    private static final int MSG_ON_HANDOVER_COMPLETE = 13;
 
     /** Default Handler used to consolidate binder method calls onto a single thread. */
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@@ -157,6 +158,11 @@
                     mPhone.internalOnHandoverFailed(callId, error);
                     break;
                 }
+                case MSG_ON_HANDOVER_COMPLETE: {
+                    String callId = (String) msg.obj;
+                    mPhone.internalOnHandoverComplete(callId);
+                    break;
+                }
                 default:
                     break;
             }
@@ -237,6 +243,11 @@
         public void onHandoverFailed(String callId, int error) {
             mHandler.obtainMessage(MSG_ON_HANDOVER_FAILED, error, 0, callId).sendToTarget();
         }
+
+        @Override
+        public void onHandoverComplete(String callId) {
+            mHandler.obtainMessage(MSG_ON_HANDOVER_COMPLETE, callId).sendToTarget();
+        }
     }
 
     private Phone.Listener mPhoneListener = new Phone.Listener() {
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index b5394b9..99f94f2 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -230,6 +230,13 @@
         }
     }
 
+    final void internalOnHandoverComplete(String callId) {
+        Call call = mCallByTelecomCallId.get(callId);
+        if (call != null) {
+            call.internalOnHandoverComplete();
+        }
+    }
+
     /**
      * Called to destroy the phone and cleanup any lingering calls.
      */
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 3f5b78a..1fe5db5 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -385,6 +385,17 @@
     public static final String EXTRA_IS_HANDOVER = "android.telecom.extra.IS_HANDOVER";
 
     /**
+     * When {@code true} indicates that a request to create a new connection is for the purpose of
+     * a handover.  Note: This is used with the
+     * {@link android.telecom.Call#handoverTo(PhoneAccountHandle, int, Bundle)} API as part of the
+     * internal communication mechanism with the {@link android.telecom.ConnectionService}.  It is
+     * not the same as the legacy {@link #EXTRA_IS_HANDOVER} extra.
+     * @hide
+     */
+    public static final String EXTRA_IS_HANDOVER_CONNECTION =
+            "android.telecom.extra.IS_HANDOVER_CONNECTION";
+
+    /**
      * Parcelable extra used with {@link #EXTRA_IS_HANDOVER} to indicate the source
      * {@link PhoneAccountHandle} when initiating a handover which {@link ConnectionService}
      * the handover is from.
diff --git a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
index 3d04bfc..3a84f00 100644
--- a/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IConnectionService.aidl
@@ -107,4 +107,6 @@
 
     void handoverFailed(String callId, in ConnectionRequest request,
             int error, in Session.Info sessionInfo);
+
+    void handoverComplete(String callId, in Session.Info sessionInfo);
 }
diff --git a/telecomm/java/com/android/internal/telecom/IInCallService.aidl b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
index 110109e..b9563fa 100644
--- a/telecomm/java/com/android/internal/telecom/IInCallService.aidl
+++ b/telecomm/java/com/android/internal/telecom/IInCallService.aidl
@@ -56,4 +56,6 @@
     void onRttInitiationFailure(String callId, int reason);
 
     void onHandoverFailed(String callId, int error);
+
+    void onHandoverComplete(String callId);
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index cbc9428..91d86c6 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -39,13 +39,29 @@
     private final static String TAG = "CarrierConfigManager";
 
     /**
+     * Extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate the slot index that the
+     * broadcast is for.
+     */
+    public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
+
+    /**
+     * Optional extra included in {@link #ACTION_CARRIER_CONFIG_CHANGED} to indicate the
+     * subscription index that the broadcast is for, if a valid one is available.
+     */
+    public static final String EXTRA_SUBSCRIPTION_INDEX =
+            SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX;
+
+    /**
      * @hide
      */
     public CarrierConfigManager() {
     }
 
     /**
-     * This intent is broadcast by the system when carrier config changes.
+     * This intent is broadcast by the system when carrier config changes. An int is specified in
+     * {@link #EXTRA_SLOT_INDEX} to indicate the slot index that this is for. An optional int extra
+     * {@link #EXTRA_SUBSCRIPTION_INDEX} is included to indicate the subscription index if a valid
+     * one is available for the slot index.
      */
     public static final String
             ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index 7f20c8a..5f1f448 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -40,6 +40,8 @@
     private final String mAlphaLong;
     // short alpha Operator Name String or Enhanced Operator Name String
     private final String mAlphaShort;
+    // cell bandwidth, in kHz
+    private final int mBandwidth;
 
     /**
      * @hide
@@ -50,6 +52,7 @@
         mPci = Integer.MAX_VALUE;
         mTac = Integer.MAX_VALUE;
         mEarfcn = Integer.MAX_VALUE;
+        mBandwidth = Integer.MAX_VALUE;
         mAlphaLong = null;
         mAlphaShort = null;
     }
@@ -65,7 +68,8 @@
      * @hide
      */
     public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac) {
-        this(ci, pci, tac, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc), null, null);
+        this(ci, pci, tac, Integer.MAX_VALUE, Integer.MAX_VALUE, String.valueOf(mcc),
+                String.valueOf(mnc), null, null);
     }
 
     /**
@@ -80,7 +84,8 @@
      * @hide
      */
     public CellIdentityLte(int mcc, int mnc, int ci, int pci, int tac, int earfcn) {
-        this(ci, pci, tac, earfcn, String.valueOf(mcc), String.valueOf(mnc), null, null);
+        this(ci, pci, tac, earfcn, Integer.MAX_VALUE, String.valueOf(mcc), String.valueOf(mnc),
+                null, null);
     }
 
     /**
@@ -89,6 +94,7 @@
      * @param pci Physical Cell Id 0..503
      * @param tac 16-bit Tracking Area Code
      * @param earfcn 18-bit LTE Absolute RF Channel Number
+     * @param bandwidth cell bandwidth in kHz
      * @param mccStr 3-digit Mobile Country Code in string format
      * @param mncStr 2 or 3-digit Mobile Network Code in string format
      * @param alphal long alpha Operator Name String or Enhanced Operator Name String
@@ -96,19 +102,20 @@
      *
      * @hide
      */
-    public CellIdentityLte(int ci, int pci, int tac, int earfcn, String mccStr,
-                            String mncStr, String alphal, String alphas) {
+    public CellIdentityLte(int ci, int pci, int tac, int earfcn, int bandwidth, String mccStr,
+            String mncStr, String alphal, String alphas) {
         super(TAG, TYPE_LTE, mccStr, mncStr);
         mCi = ci;
         mPci = pci;
         mTac = tac;
         mEarfcn = earfcn;
+        mBandwidth = bandwidth;
         mAlphaLong = alphal;
         mAlphaShort = alphas;
     }
 
     private CellIdentityLte(CellIdentityLte cid) {
-        this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mMccStr,
+        this(cid.mCi, cid.mPci, cid.mTac, cid.mEarfcn, cid.mBandwidth, cid.mMccStr,
                 cid.mMncStr, cid.mAlphaLong, cid.mAlphaShort);
     }
 
@@ -163,6 +170,13 @@
     }
 
     /**
+     * @return Cell bandwidth in kHz, Integer.MAX_VALUE if unknown
+     */
+    public int getBandwidth() {
+        return mBandwidth;
+    }
+
+    /**
      * @return Mobile Country Code in string format, null if unknown
      */
     public String getMccStr() {
@@ -219,6 +233,7 @@
                 && mPci == o.mPci
                 && mTac == o.mTac
                 && mEarfcn == o.mEarfcn
+                && mBandwidth == o.mBandwidth
                 && TextUtils.equals(mMccStr, o.mMccStr)
                 && TextUtils.equals(mMncStr, o.mMncStr)
                 && TextUtils.equals(mAlphaLong, o.mAlphaLong)
@@ -232,6 +247,7 @@
         .append(" mPci=").append(mPci)
         .append(" mTac=").append(mTac)
         .append(" mEarfcn=").append(mEarfcn)
+        .append(" mBandwidth=").append(mBandwidth)
         .append(" mMcc=").append(mMccStr)
         .append(" mMnc=").append(mMncStr)
         .append(" mAlphaLong=").append(mAlphaLong)
@@ -248,6 +264,7 @@
         dest.writeInt(mPci);
         dest.writeInt(mTac);
         dest.writeInt(mEarfcn);
+        dest.writeInt(mBandwidth);
         dest.writeString(mAlphaLong);
         dest.writeString(mAlphaShort);
     }
@@ -259,6 +276,7 @@
         mPci = in.readInt();
         mTac = in.readInt();
         mEarfcn = in.readInt();
+        mBandwidth = in.readInt();
         mAlphaLong = in.readString();
         mAlphaShort = in.readString();
 
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index b5e4eef..9232ed7 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -16,8 +16,11 @@
 
 package android.telephony;
 
+import android.annotation.IntDef;
 import android.os.Parcel;
 import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 
 /**
  * Immutable cell information from a point in time.
@@ -47,6 +50,34 @@
     /** @hide */
     public static final int TIMESTAMP_TYPE_JAVA_RIL = 4;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+        CONNECTION_NONE,
+        CONNECTION_PRIMARY_SERVING,
+        CONNECTION_SECONDARY_SERVING,
+        CONNECTION_UNKNOWN
+    })
+    public @interface CellConnectionStatus {}
+
+    /**
+     * Cell is not a serving cell.
+     *
+     * <p>The cell has been measured but is neither a camped nor serving cell (3GPP 36.304).
+     */
+    public static final int CONNECTION_NONE = 0;
+
+    /** UE is connected to cell for signalling and possibly data (3GPP 36.331, 25.331). */
+    public static final int CONNECTION_PRIMARY_SERVING = 1;
+
+    /** UE is connected to cell for data (3GPP 36.331, 25.331). */
+    public static final int CONNECTION_SECONDARY_SERVING = 2;
+
+    /** Connection status is unknown. */
+    public static final int CONNECTION_UNKNOWN = Integer.MAX_VALUE;
+
+    private int mCellConnectionStatus = CONNECTION_NONE;
+
     // True if device is mRegistered to the mobile network
     private boolean mRegistered;
 
@@ -69,6 +100,7 @@
         this.mRegistered = ci.mRegistered;
         this.mTimeStampType = ci.mTimeStampType;
         this.mTimeStamp = ci.mTimeStamp;
+        this.mCellConnectionStatus = ci.mCellConnectionStatus;
     }
 
     /** True if this cell is registered to the mobile network */
@@ -90,6 +122,25 @@
     }
 
     /**
+     * Gets the connection status of this cell.
+     *
+     * @see #CONNECTION_NONE
+     * @see #CONNECTION_PRIMARY_SERVING
+     * @see #CONNECTION_SECONDARY_SERVING
+     * @see #CONNECTION_UNKNOWN
+     *
+     * @return The connection status of the cell.
+     */
+    @CellConnectionStatus
+    public int getCellConnectionStatus() {
+        return mCellConnectionStatus;
+    }
+    /** @hide */
+    public void setCellConnectionStatus(@CellConnectionStatus int cellConnectionStatus) {
+        mCellConnectionStatus = cellConnectionStatus;
+    }
+
+    /**
      * Where time stamp gets recorded.
      * @return one of TIMESTAMP_TYPE_XXXX
      *
@@ -111,7 +162,7 @@
     public int hashCode() {
         int primeNum = 31;
         return ((mRegistered ? 0 : 1) * primeNum) + ((int)(mTimeStamp / 1000) * primeNum)
-                + (mTimeStampType * primeNum);
+                + (mTimeStampType * primeNum) + (mCellConnectionStatus * primeNum);
     }
 
     @Override
@@ -125,7 +176,9 @@
         try {
             CellInfo o = (CellInfo) other;
             return mRegistered == o.mRegistered
-                    && mTimeStamp == o.mTimeStamp && mTimeStampType == o.mTimeStampType;
+                    && mTimeStamp == o.mTimeStamp
+                    && mTimeStampType == o.mTimeStampType
+                    && mCellConnectionStatus == o.mCellConnectionStatus;
         } catch (ClassCastException e) {
             return false;
         }
@@ -155,6 +208,7 @@
         timeStampType = timeStampTypeToString(mTimeStampType);
         sb.append(" mTimeStampType=").append(timeStampType);
         sb.append(" mTimeStamp=").append(mTimeStamp).append("ns");
+        sb.append(" mCellConnectionStatus=").append(mCellConnectionStatus);
 
         return sb.toString();
     }
@@ -181,6 +235,7 @@
         dest.writeInt(mRegistered ? 1 : 0);
         dest.writeInt(mTimeStampType);
         dest.writeLong(mTimeStamp);
+        dest.writeInt(mCellConnectionStatus);
     }
 
     /**
@@ -192,6 +247,7 @@
         mRegistered = (in.readInt() == 1) ? true : false;
         mTimeStampType = in.readInt();
         mTimeStamp = in.readLong();
+        mCellConnectionStatus = in.readInt();
     }
 
     /** Implement the Parcelable interface */
diff --git a/telephony/java/android/telephony/INetworkService.aidl b/telephony/java/android/telephony/INetworkService.aidl
index d810d58..9ef7186 100644
--- a/telephony/java/android/telephony/INetworkService.aidl
+++ b/telephony/java/android/telephony/INetworkService.aidl
@@ -23,7 +23,9 @@
  */
 oneway interface INetworkService
 {
-    void getNetworkRegistrationState(int domain, INetworkServiceCallback callback);
-    void registerForNetworkRegistrationStateChanged(INetworkServiceCallback callback);
-    void unregisterForNetworkRegistrationStateChanged(INetworkServiceCallback callback);
+    void createNetworkServiceProvider(int slotId);
+    void removeNetworkServiceProvider(int slotId);
+    void getNetworkRegistrationState(int slotId, int domain, INetworkServiceCallback callback);
+    void registerForNetworkRegistrationStateChanged(int slotId, INetworkServiceCallback callback);
+    void unregisterForNetworkRegistrationStateChanged(int slotId, INetworkServiceCallback callback);
 }
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
index 6b3584c..94921de 100644
--- a/telephony/java/android/telephony/NetworkService.java
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -53,11 +53,13 @@
     public static final String NETWORK_SERVICE_INTERFACE = "android.telephony.NetworkService";
     public static final String NETWORK_SERVICE_EXTRA_SLOT_ID = "android.telephony.extra.SLOT_ID";
 
-    private static final int NETWORK_SERVICE_INTERNAL_REQUEST_INITIALIZE_SERVICE       = 1;
-    private static final int NETWORK_SERVICE_GET_REGISTRATION_STATE                    = 2;
-    private static final int NETWORK_SERVICE_REGISTER_FOR_STATE_CHANGE                 = 3;
-    private static final int NETWORK_SERVICE_UNREGISTER_FOR_STATE_CHANGE               = 4;
-    private static final int NETWORK_SERVICE_INDICATION_NETWORK_STATE_CHANGED          = 5;
+    private static final int NETWORK_SERVICE_CREATE_NETWORK_SERVICE_PROVIDER                 = 1;
+    private static final int NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER                 = 2;
+    private static final int NETWORK_SERVICE_REMOVE_ALL_NETWORK_SERVICE_PROVIDERS            = 3;
+    private static final int NETWORK_SERVICE_GET_REGISTRATION_STATE                          = 4;
+    private static final int NETWORK_SERVICE_REGISTER_FOR_STATE_CHANGE                       = 5;
+    private static final int NETWORK_SERVICE_UNREGISTER_FOR_STATE_CHANGE                     = 6;
+    private static final int NETWORK_SERVICE_INDICATION_NETWORK_STATE_CHANGED                = 7;
 
 
     private final HandlerThread mHandlerThread;
@@ -66,7 +68,7 @@
 
     private final SparseArray<NetworkServiceProvider> mServiceMap = new SparseArray<>();
 
-    private final SparseArray<INetworkServiceWrapper> mBinderMap = new SparseArray<>();
+    private final INetworkServiceWrapper mBinder = new INetworkServiceWrapper();
 
     /**
      * The abstract class of the actual network service implementation. The network service provider
@@ -147,37 +149,50 @@
         public void handleMessage(Message message) {
             final int slotId = message.arg1;
             final INetworkServiceCallback callback = (INetworkServiceCallback) message.obj;
-            NetworkServiceProvider service;
 
-            synchronized (mServiceMap) {
-                service = mServiceMap.get(slotId);
-            }
+            NetworkServiceProvider serviceProvider = mServiceMap.get(slotId);
 
             switch (message.what) {
-                case NETWORK_SERVICE_INTERNAL_REQUEST_INITIALIZE_SERVICE:
-                    service = createNetworkServiceProvider(message.arg1);
-                    if (service != null) {
-                        mServiceMap.put(slotId, service);
+                case NETWORK_SERVICE_CREATE_NETWORK_SERVICE_PROVIDER:
+                    // If the service provider doesn't exist yet, we try to create it.
+                    if (serviceProvider == null) {
+                        mServiceMap.put(slotId, createNetworkServiceProvider(slotId));
                     }
                     break;
+                case NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER:
+                    // If the service provider doesn't exist yet, we try to create it.
+                    if (serviceProvider != null) {
+                        serviceProvider.onDestroy();
+                        mServiceMap.remove(slotId);
+                    }
+                    break;
+                case NETWORK_SERVICE_REMOVE_ALL_NETWORK_SERVICE_PROVIDERS:
+                    for (int i = 0; i < mServiceMap.size(); i++) {
+                        serviceProvider = mServiceMap.get(i);
+                        if (serviceProvider != null) {
+                            serviceProvider.onDestroy();
+                        }
+                    }
+                    mServiceMap.clear();
+                    break;
                 case NETWORK_SERVICE_GET_REGISTRATION_STATE:
-                    if (service == null) break;
+                    if (serviceProvider == null) break;
                     int domainId = message.arg2;
-                    service.getNetworkRegistrationState(domainId,
+                    serviceProvider.getNetworkRegistrationState(domainId,
                             new NetworkServiceCallback(callback));
 
                     break;
                 case NETWORK_SERVICE_REGISTER_FOR_STATE_CHANGE:
-                    if (service == null) break;
-                    service.registerForStateChanged(callback);
+                    if (serviceProvider == null) break;
+                    serviceProvider.registerForStateChanged(callback);
                     break;
                 case NETWORK_SERVICE_UNREGISTER_FOR_STATE_CHANGE:
-                    if (service == null) break;
-                    service.unregisterForStateChanged(callback);
+                    if (serviceProvider == null) break;
+                    serviceProvider.unregisterForStateChanged(callback);
                     break;
                 case NETWORK_SERVICE_INDICATION_NETWORK_STATE_CHANGED:
-                    if (service == null) break;
-                    service.notifyStateChangedToCallbacks();
+                    if (serviceProvider == null) break;
+                    serviceProvider.notifyStateChangedToCallbacks();
                     break;
                 default:
                     break;
@@ -212,47 +227,14 @@
             return null;
         }
 
-        int slotId = intent.getIntExtra(
-                NETWORK_SERVICE_EXTRA_SLOT_ID, SubscriptionManager.INVALID_SIM_SLOT_INDEX);
-
-        if (!SubscriptionManager.isValidSlotIndex(slotId)) {
-            loge("Invalid slot id " + slotId);
-            return null;
-        }
-
-        log("onBind: slot id=" + slotId);
-
-        INetworkServiceWrapper binder = mBinderMap.get(slotId);
-        if (binder == null) {
-            Message msg = mHandler.obtainMessage(
-                    NETWORK_SERVICE_INTERNAL_REQUEST_INITIALIZE_SERVICE);
-            msg.arg1 = slotId;
-            msg.sendToTarget();
-
-            binder = new INetworkServiceWrapper(slotId);
-            mBinderMap.put(slotId, binder);
-        }
-
-        return binder;
+        return mBinder;
     }
 
     /** @hide */
     @Override
     public boolean onUnbind(Intent intent) {
-        int slotId = intent.getIntExtra(NETWORK_SERVICE_EXTRA_SLOT_ID,
-                SubscriptionManager.INVALID_SIM_SLOT_INDEX);
-        if (mBinderMap.get(slotId) != null) {
-            NetworkServiceProvider serviceImpl;
-            synchronized (mServiceMap) {
-                serviceImpl = mServiceMap.get(slotId);
-            }
-            // We assume only one component might bind to the service. So if onUnbind is ever
-            // called, we destroy the serviceImpl.
-            if (serviceImpl != null) {
-                serviceImpl.onDestroy();
-            }
-            mBinderMap.remove(slotId);
-        }
+        mHandler.obtainMessage(NETWORK_SERVICE_REMOVE_ALL_NETWORK_SERVICE_PROVIDERS, 0,
+                0, null).sendToTarget();
 
         return false;
     }
@@ -260,16 +242,6 @@
     /** @hide */
     @Override
     public void onDestroy() {
-        synchronized (mServiceMap) {
-            for (int i = 0; i < mServiceMap.size(); i++) {
-                NetworkServiceProvider serviceImpl = mServiceMap.get(i);
-                if (serviceImpl != null) {
-                    serviceImpl.onDestroy();
-                }
-            }
-            mServiceMap.clear();
-        }
-
         mHandlerThread.quit();
     }
 
@@ -279,27 +251,36 @@
      */
     private class INetworkServiceWrapper extends INetworkService.Stub {
 
-        private final int mSlotId;
-
-        INetworkServiceWrapper(int slotId) {
-            mSlotId = slotId;
+        @Override
+        public void createNetworkServiceProvider(int slotId) {
+            mHandler.obtainMessage(NETWORK_SERVICE_CREATE_NETWORK_SERVICE_PROVIDER, slotId,
+                    0, null).sendToTarget();
         }
 
         @Override
-        public void getNetworkRegistrationState(int domain, INetworkServiceCallback callback) {
-            mHandler.obtainMessage(NETWORK_SERVICE_GET_REGISTRATION_STATE, mSlotId,
+        public void removeNetworkServiceProvider(int slotId) {
+            mHandler.obtainMessage(NETWORK_SERVICE_REMOVE_NETWORK_SERVICE_PROVIDER, slotId,
+                    0, null).sendToTarget();
+        }
+
+        @Override
+        public void getNetworkRegistrationState(
+                int slotId, int domain, INetworkServiceCallback callback) {
+            mHandler.obtainMessage(NETWORK_SERVICE_GET_REGISTRATION_STATE, slotId,
                     domain, callback).sendToTarget();
         }
 
         @Override
-        public void registerForNetworkRegistrationStateChanged(INetworkServiceCallback callback) {
-            mHandler.obtainMessage(NETWORK_SERVICE_REGISTER_FOR_STATE_CHANGE, mSlotId,
+        public void registerForNetworkRegistrationStateChanged(
+                int slotId, INetworkServiceCallback callback) {
+            mHandler.obtainMessage(NETWORK_SERVICE_REGISTER_FOR_STATE_CHANGE, slotId,
                     0, callback).sendToTarget();
         }
 
         @Override
-        public void unregisterForNetworkRegistrationStateChanged(INetworkServiceCallback callback) {
-            mHandler.obtainMessage(NETWORK_SERVICE_UNREGISTER_FOR_STATE_CHANGE, mSlotId,
+        public void unregisterForNetworkRegistrationStateChanged(
+                int slotId,INetworkServiceCallback callback) {
+            mHandler.obtainMessage(NETWORK_SERVICE_UNREGISTER_FOR_STATE_CHANGE, slotId,
                     0, callback).sendToTarget();
         }
     }
@@ -311,4 +292,4 @@
     private final void loge(String s) {
         Rlog.e(TAG, s);
     }
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 77706e8..90a3677 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -17,6 +17,7 @@
 package android.telephony;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -25,6 +26,7 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -36,6 +38,7 @@
  *
  * <ul>
  *   <li>Service state: IN_SERVICE, OUT_OF_SERVICE, EMERGENCY_ONLY, POWER_OFF
+ *   <li>Duplex mode: UNKNOWN, FDD, TDD
  *   <li>Roaming indicator
  *   <li>Operator name, short name and numeric id
  *   <li>Network selection mode
@@ -71,6 +74,26 @@
      */
     public static final int STATE_POWER_OFF = 3;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({DUPLEX_MODE_UNKNOWN, DUPLEX_MODE_FDD, DUPLEX_MODE_TDD})
+    public @interface DuplexMode {}
+
+    /**
+     * Duplex mode for the phone is unknown.
+     */
+    public static final int DUPLEX_MODE_UNKNOWN = 0;
+
+    /**
+     * Duplex mode for the phone is frequency-division duplexing.
+     */
+    public static final int DUPLEX_MODE_FDD = 1;
+
+    /**
+     * Duplex mode for the phone is time-division duplexing.
+     */
+    public static final int DUPLEX_MODE_TDD = 2;
+
     /**
      * RIL level registration state values from ril.h
      * ((const char **)response)[0] is registration state 0-6,
@@ -286,6 +309,9 @@
 
     private boolean mIsUsingCarrierAggregation;
 
+    private int mChannelNumber;
+    private int[] mCellBandwidths = new int[0];
+
     /* EARFCN stands for E-UTRA Absolute Radio Frequency Channel Number,
      * Reference: 3GPP TS 36.104 5.4.3 */
     private int mLteEarfcnRsrpBoost = 0;
@@ -405,6 +431,8 @@
         mLteEarfcnRsrpBoost = in.readInt();
         mNetworkRegistrationStates = new ArrayList<>();
         in.readList(mNetworkRegistrationStates, NetworkRegistrationState.class.getClassLoader());
+        mChannelNumber = in.readInt();
+        mCellBandwidths = in.createIntArray();
     }
 
     public void writeToParcel(Parcel out, int flags) {
@@ -433,6 +461,8 @@
         out.writeInt(mIsUsingCarrierAggregation ? 1 : 0);
         out.writeInt(mLteEarfcnRsrpBoost);
         out.writeList(mNetworkRegistrationStates);
+        out.writeInt(mChannelNumber);
+        out.writeIntArray(mCellBandwidths);
     }
 
     public int describeContents() {
@@ -486,6 +516,43 @@
     }
 
     /**
+     * Get the current duplex mode
+     *
+     * @see #DUPLEX_MODE_UNKNOWN
+     * @see #DUPLEX_MODE_FDD
+     * @see #DUPLEX_MODE_TDD
+     *
+     * @return Current {@code DuplexMode} for the phone
+     */
+    @DuplexMode
+    public int getDuplexMode() {
+        // TODO(b/72117602) determine duplex mode from channel number, using 3GPP 36.101 sections
+        // 5.7.3-1 and 5.5-1
+        return DUPLEX_MODE_UNKNOWN;
+    }
+
+    /**
+     * Get the channel number of the current primary serving cell, or -1 if unknown
+     *
+     * <p>This is EARFCN for LTE, UARFCN for UMTS, and ARFCN for GSM.
+     *
+     * @return Channel number of primary serving cell
+     */
+    public int getChannelNumber() {
+        return mChannelNumber;
+    }
+
+    /**
+     * Get an array of cell bandwidths (kHz) for the current serving cells
+     *
+     * @return Current serving cell bandwidths
+     */
+    @Nullable
+    public int[] getCellBandwidths() {
+        return mCellBandwidths;
+    }
+
+    /**
      * Get current roaming indicator of phone
      * (note: not just decoding from TS 27.007 7.2)
      *
@@ -713,6 +780,8 @@
                 + (mDataRegState * 37)
                 + mVoiceRoamingType
                 + mDataRoamingType
+                + mChannelNumber
+                + Arrays.hashCode(mCellBandwidths)
                 + (mIsManualNetworkSelection ? 1 : 0)
                 + ((null == mVoiceOperatorAlphaLong) ? 0 : mVoiceOperatorAlphaLong.hashCode())
                 + ((null == mVoiceOperatorAlphaShort) ? 0 : mVoiceOperatorAlphaShort.hashCode())
@@ -745,6 +814,8 @@
                 && mIsManualNetworkSelection == s.mIsManualNetworkSelection
                 && mVoiceRoamingType == s.mVoiceRoamingType
                 && mDataRoamingType == s.mDataRoamingType
+                && mChannelNumber == s.mChannelNumber
+                && Arrays.equals(mCellBandwidths, s.mCellBandwidths)
                 && equalsHandlesNulls(mVoiceOperatorAlphaLong, s.mVoiceOperatorAlphaLong)
                 && equalsHandlesNulls(mVoiceOperatorAlphaShort, s.mVoiceOperatorAlphaShort)
                 && equalsHandlesNulls(mVoiceOperatorNumeric, s.mVoiceOperatorNumeric)
@@ -874,6 +945,8 @@
             .append("(" + rilServiceStateToString(mVoiceRegState) + ")")
             .append(", mDataRegState=").append(mDataRegState)
             .append("(" + rilServiceStateToString(mDataRegState) + ")")
+            .append(", mChannelNumber=").append(mChannelNumber)
+            .append(", mCellBandwidths=").append(Arrays.toString(mCellBandwidths))
             .append(", mVoiceRoamingType=").append(getRoamingLogString(mVoiceRoamingType))
             .append(", mDataRoamingType=").append(getRoamingLogString(mDataRoamingType))
             .append(", mVoiceOperatorAlphaLong=").append(mVoiceOperatorAlphaLong)
@@ -905,6 +978,8 @@
         mDataRegState = state;
         mVoiceRoamingType = ROAMING_TYPE_NOT_ROAMING;
         mDataRoamingType = ROAMING_TYPE_NOT_ROAMING;
+        mChannelNumber = -1;
+        mCellBandwidths = new int[0];
         mVoiceOperatorAlphaLong = null;
         mVoiceOperatorAlphaShort = null;
         mVoiceOperatorNumeric = null;
@@ -953,6 +1028,16 @@
         if (VDBG) Rlog.d(LOG_TAG, "[ServiceState] setDataRegState=" + mDataRegState);
     }
 
+    /** @hide */
+    public void setCellBandwidths(int[] bandwidths) {
+        mCellBandwidths = bandwidths;
+    }
+
+    /** @hide */
+    public void setChannelNumber(int channelNumber) {
+        mChannelNumber = channelNumber;
+    }
+
     public void setRoaming(boolean roaming) {
         mVoiceRoamingType = (roaming ? ROAMING_TYPE_UNKNOWN : ROAMING_TYPE_NOT_ROAMING);
         mDataRoamingType = mVoiceRoamingType;
@@ -1101,6 +1186,8 @@
         mIsDataRoamingFromRegistration = m.getBoolean("isDataRoamingFromRegistration");
         mIsUsingCarrierAggregation = m.getBoolean("isUsingCarrierAggregation");
         mLteEarfcnRsrpBoost = m.getInt("LteEarfcnRsrpBoost");
+        mChannelNumber = m.getInt("ChannelNumber");
+        mCellBandwidths = m.getIntArray("CellBandwidths");
     }
 
     /**
@@ -1132,6 +1219,8 @@
         m.putBoolean("isDataRoamingFromRegistration", mIsDataRoamingFromRegistration);
         m.putBoolean("isUsingCarrierAggregation", mIsUsingCarrierAggregation);
         m.putInt("LteEarfcnRsrpBoost", mLteEarfcnRsrpBoost);
+        m.putInt("ChannelNumber", mChannelNumber);
+        m.putIntArray("CellBandwidths", mCellBandwidths);
     }
 
     /** @hide */
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 5d88cf0..0874b86 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -1134,7 +1134,11 @@
 
     // SMS send failure result codes
 
-    /** No error. {@hide}*/
+    /**
+     * No error.
+     * @hide
+     */
+    @SystemApi
     static public final int RESULT_ERROR_NONE    = 0;
     /** Generic failure cause */
     static public final int RESULT_ERROR_GENERIC_FAILURE    = 1;
@@ -1146,12 +1150,113 @@
     static public final int RESULT_ERROR_NO_SERVICE         = 4;
     /** Failed because we reached the sending queue limit. */
     static public final int RESULT_ERROR_LIMIT_EXCEEDED     = 5;
-    /** Failed because FDN is enabled. {@hide} */
+    /**
+     * Failed because FDN is enabled.
+     * @hide
+     */
+    @SystemApi
     static public final int RESULT_ERROR_FDN_CHECK_FAILURE  = 6;
     /** Failed because user denied the sending of this short code. */
     static public final int RESULT_ERROR_SHORT_CODE_NOT_ALLOWED = 7;
     /** Failed because the user has denied this app ever send premium short codes. */
     static public final int RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED = 8;
+    /**
+     * Failed because the radio was not available
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_RADIO_NOT_AVAILABLE = 9;
+    /**
+     * Failed because of network rejection
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_NETWORK_REJECT = 10;
+    /**
+     * Failed because of invalid arguments
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_INVALID_ARGUMENTS = 11;
+    /**
+     * Failed because of an invalid state
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_INVALID_STATE = 12;
+    /**
+     * Failed because there is no memory
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_NO_MEMORY = 13;
+    /**
+     * Failed because the sms format is not valid
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_INVALID_SMS_FORMAT = 14;
+    /**
+     * Failed because of a system error
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_SYSTEM_ERROR = 15;
+    /**
+     * Failed because of a modem error
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_MODEM_ERROR = 16;
+    /**
+     * Failed because of a network error
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_NETWORK_ERROR = 17;
+    /**
+     * Failed because of an encoding error
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_ENCODING_ERROR = 18;
+    /**
+     * Failed because of an invalid smsc address
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_INVALID_SMSC_ADDRESS = 19;
+    /**
+     * Failed because the operation is not allowed
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_OPERATION_NOT_ALLOWED = 20;
+    /**
+     * Failed because of an internal error
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_INTERNAL_ERROR = 21;
+    /**
+     * Failed because there are no resources
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_NO_RESOURCES = 22;
+    /**
+     * Failed because the operation was cancelled
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_CANCELLED = 23;
+    /**
+     * Failed because the request is not supported
+     * @hide
+     */
+    @SystemApi
+    static public final int RESULT_REQUEST_NOT_SUPPORTED = 24;
+
 
     static private final String PHONE_PACKAGE_NAME = "com.android.phone";
 
diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
index 5197107..93c316f 100644
--- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
@@ -209,6 +209,13 @@
                 return MMTelFeature.this.getSmsFormat();
             }
         }
+
+        @Override
+        public void onSmsReady() {
+            synchronized (mLock) {
+                MMTelFeature.this.onSmsReady();
+            }
+        }
     };
 
     /**
@@ -384,25 +391,29 @@
         return null;
     }
 
-    public void setSmsListener(IImsSmsListener listener) {
+    private void setSmsListener(IImsSmsListener listener) {
         getSmsImplementation().registerSmsListener(listener);
     }
 
-    public void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
+    private void sendSms(int token, int messageRef, String format, String smsc, boolean isRetry,
             byte[] pdu) {
         getSmsImplementation().sendSms(token, messageRef, format, smsc, isRetry, pdu);
     }
 
-    public void acknowledgeSms(int token, int messageRef,
+    private void acknowledgeSms(int token, int messageRef,
             @SmsImplBase.DeliverStatusResult int result) {
         getSmsImplementation().acknowledgeSms(token, messageRef, result);
     }
 
-    public void acknowledgeSmsReport(int token, int messageRef,
+    private void acknowledgeSmsReport(int token, int messageRef,
             @SmsImplBase.StatusReportResult int result) {
         getSmsImplementation().acknowledgeSmsReport(token, messageRef, result);
     }
 
+    private void onSmsReady() {
+        getSmsImplementation().onReady();
+    }
+
     /**
      * Must be overridden by IMS Provider to be able to support SMS over IMS. Otherwise a default
      * non-functional implementation is returned.
diff --git a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java
index 113dad4..89acc80 100644
--- a/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java
+++ b/telephony/java/android/telephony/ims/internal/stub/SmsImplBase.java
@@ -17,10 +17,10 @@
 package android.telephony.ims.internal.stub;
 
 import android.annotation.IntDef;
+import android.annotation.SystemApi;
 import android.os.RemoteException;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage;
-import android.telephony.ims.internal.feature.MmTelFeature;
 import android.util.Log;
 
 import com.android.ims.internal.IImsSmsListener;
@@ -33,11 +33,14 @@
  *
  * Any service wishing to provide SMS over IMS should extend this class and implement all methods
  * that the service supports.
+ *
  * @hide
  */
+@SystemApi
 public class SmsImplBase {
     private static final String LOG_TAG = "SmsImplBase";
 
+    /** @hide */
     @IntDef({
             SEND_STATUS_OK,
             SEND_STATUS_ERROR,
@@ -58,8 +61,8 @@
     public static final int SEND_STATUS_ERROR = 2;
 
     /**
-     * IMS provider failed to send the message and platform should retry again after setting TP-RD bit
-     * to high.
+     * IMS provider failed to send the message and platform should retry again after setting TP-RD
+     * bit to high.
      */
     public static final int SEND_STATUS_ERROR_RETRY = 3;
 
@@ -69,6 +72,7 @@
      */
     public static final int SEND_STATUS_ERROR_FALLBACK = 4;
 
+    /** @hide */
     @IntDef({
             DELIVER_STATUS_OK,
             DELIVER_STATUS_ERROR
@@ -85,6 +89,7 @@
      */
     public static final int DELIVER_STATUS_ERROR = 2;
 
+    /** @hide */
     @IntDef({
             STATUS_REPORT_STATUS_OK,
             STATUS_REPORT_STATUS_ERROR
@@ -140,21 +145,23 @@
         try {
             onSendSmsResult(token, messageRef, SEND_STATUS_ERROR,
                     SmsManager.RESULT_ERROR_GENERIC_FAILURE);
-        } catch (RemoteException e) {
+        } catch (RuntimeException e) {
             Log.e(LOG_TAG, "Can not send sms: " + e.getMessage());
         }
     }
 
     /**
-     * This method will be triggered by the platform after {@link #onSmsReceived(int, String, byte[])}
-     * has been called to deliver the result to the IMS provider.
+     * This method will be triggered by the platform after
+     * {@link #onSmsReceived(int, String, byte[])} has been called to deliver the result to the IMS
+     * provider.
      *
      * @param token token provided in {@link #onSmsReceived(int, String, byte[])}
-     * @param result result of delivering the message. Valid values are defined in
-     * {@link DeliverStatusResult}
+     * @param result result of delivering the message. Valid values are:
+     *  {@link #DELIVER_STATUS_OK},
+     *  {@link #DELIVER_STATUS_OK}
      * @param messageRef the message reference
      */
-    public void acknowledgeSms(int token, int messageRef, @DeliverStatusResult int result) {
+    public void acknowledgeSms(int token, @DeliverStatusResult int messageRef, int result) {
         Log.e(LOG_TAG, "acknowledgeSms() not implemented.");
     }
 
@@ -164,8 +171,9 @@
      * result to the IMS provider.
      *
      * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
-     * @param result result of delivering the message. Valid values are defined in
-     * {@link StatusReportResult}
+     * @param result result of delivering the message. Valid values are:
+     *  {@link #STATUS_REPORT_STATUS_OK},
+     *  {@link #STATUS_REPORT_STATUS_ERROR}
      * @param messageRef the message reference
      */
     public void acknowledgeSmsReport(int token, int messageRef, @StatusReportResult int result) {
@@ -177,20 +185,17 @@
      * platform will deliver the message to the messages database and notify the IMS provider of the
      * result by calling {@link #acknowledgeSms(int, int, int)}.
      *
-     * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
-     *
      * @param token unique token generated by IMS providers that the platform will use to trigger
      *              callbacks for this message.
      * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
      * {@link SmsMessage#FORMAT_3GPP2}.
      * @param pdu PDUs representing the contents of the message.
-     * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+     * @throws RuntimeException if called before {@link #onReady()} is triggered.
      */
-    public final void onSmsReceived(int token, String format, byte[] pdu)
-            throws IllegalStateException {
+    public final void onSmsReceived(int token, String format, byte[] pdu) throws RuntimeException {
         synchronized (mLock) {
             if (mListener == null) {
-                throw new IllegalStateException("Feature not ready.");
+                throw new RuntimeException("Feature not ready.");
             }
             try {
                 mListener.onSmsReceived(token, format, pdu);
@@ -205,11 +210,13 @@
      * This method should be triggered by the IMS providers to pass the result of the sent message
      * to the platform.
      *
-     * This method must not be called before {@link MmTelFeature#onFeatureReady()} is called.
-     *
      * @param token token provided in {@link #sendSms(int, int, String, String, boolean, byte[])}
      * @param messageRef the message reference. Should be between 0 and 255 per TS.123.040
-     * @param status result of sending the SMS. Valid values are defined in {@link SendStatusResult}
+     * @param status result of sending the SMS. Valid values are:
+     *  {@link #SEND_STATUS_OK},
+     *  {@link #SEND_STATUS_ERROR},
+     *  {@link #SEND_STATUS_ERROR_RETRY},
+     *  {@link #SEND_STATUS_ERROR_FALLBACK},
      * @param reason reason in case status is failure. Valid values are:
      *  {@link SmsManager#RESULT_ERROR_NONE},
      *  {@link SmsManager#RESULT_ERROR_GENERIC_FAILURE},
@@ -217,19 +224,41 @@
      *  {@link SmsManager#RESULT_ERROR_NULL_PDU},
      *  {@link SmsManager#RESULT_ERROR_NO_SERVICE},
      *  {@link SmsManager#RESULT_ERROR_LIMIT_EXCEEDED},
+     *  {@link SmsManager#RESULT_ERROR_FDN_CHECK_FAILURE},
      *  {@link SmsManager#RESULT_ERROR_SHORT_CODE_NOT_ALLOWED},
-     *  {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED}
-     * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
-     * @throws RemoteException if the connection to the framework is not available. If this happens
-     *  attempting to send the SMS should be aborted.
+     *  {@link SmsManager#RESULT_ERROR_SHORT_CODE_NEVER_ALLOWED},
+     *  {@link SmsManager#RESULT_RADIO_NOT_AVAILABLE},
+     *  {@link SmsManager#RESULT_NETWORK_REJECT},
+     *  {@link SmsManager#RESULT_INVALID_ARGUMENTS},
+     *  {@link SmsManager#RESULT_INVALID_STATE},
+     *  {@link SmsManager#RESULT_NO_MEMORY},
+     *  {@link SmsManager#RESULT_INVALID_SMS_FORMAT},
+     *  {@link SmsManager#RESULT_SYSTEM_ERROR},
+     *  {@link SmsManager#RESULT_MODEM_ERROR},
+     *  {@link SmsManager#RESULT_NETWORK_ERROR},
+     *  {@link SmsManager#RESULT_ENCODING_ERROR},
+     *  {@link SmsManager#RESULT_INVALID_SMSC_ADDRESS},
+     *  {@link SmsManager#RESULT_OPERATION_NOT_ALLOWED},
+     *  {@link SmsManager#RESULT_INTERNAL_ERROR},
+     *  {@link SmsManager#RESULT_NO_RESOURCES},
+     *  {@link SmsManager#RESULT_CANCELLED},
+     *  {@link SmsManager#RESULT_REQUEST_NOT_SUPPORTED}
+     *
+     * @throws RuntimeException if called before {@link #onReady()} is triggered or if the
+     * connection to the framework is not available. If this happens attempting to send the SMS
+     * should be aborted.
      */
-    public final void onSendSmsResult(int token, int messageRef, @SendStatusResult int status,
-            int reason) throws IllegalStateException, RemoteException {
+    public final void onSendSmsResult(int token, int messageRef,  @SendStatusResult int status,
+            int reason) throws RuntimeException {
         synchronized (mLock) {
             if (mListener == null) {
-                throw new IllegalStateException("Feature not ready.");
+                throw new RuntimeException("Feature not ready.");
             }
-            mListener.onSendSmsResult(token, messageRef, status, reason);
+            try {
+                mListener.onSendSmsResult(token, messageRef, status, reason);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -241,13 +270,13 @@
      * @param format the format of the message. Valid values are {@link SmsMessage#FORMAT_3GPP} and
      * {@link SmsMessage#FORMAT_3GPP2}.
      * @param pdu PDUs representing the content of the status report.
-     * @throws IllegalStateException if called before {@link MmTelFeature#onFeatureReady()}
+     * @throws RuntimeException if called before {@link #onReady()} is triggered
      */
     public final void onSmsStatusReportReceived(int token, int messageRef, String format,
-            byte[] pdu) {
+            byte[] pdu) throws RuntimeException{
         synchronized (mLock) {
             if (mListener == null) {
-                throw new IllegalStateException("Feature not ready.");
+                throw new RuntimeException("Feature not ready.");
             }
             try {
                 mListener.onSmsStatusReportReceived(token, messageRef, format, pdu);
@@ -268,4 +297,13 @@
     public String getSmsFormat() {
       return SmsMessage.FORMAT_3GPP;
     }
+
+    /**
+     * Called when SmsImpl has been initialized and communication with the framework is set up.
+     * Any attempt by this class to access the framework before this method is called will return
+     * with an {@link RuntimeException}.
+     */
+    public void onReady() {
+        // Base Implementation - Should be overridden
+    }
 }
diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
index cce39f4..10c7f3e 100644
--- a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
+++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
@@ -61,4 +61,5 @@
     oneway void acknowledgeSms(int token, int messageRef, int result);
     oneway void acknowledgeSmsReport(int token, int messageRef, int result);
     String getSmsFormat();
+    oneway void onSmsReady();
 }
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index f804cb0..cdee9e6 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -105,6 +105,8 @@
     int DEVICE_IN_USE = 64;                   /* Operation cannot be performed because the device
                                                  is currently in use */
     int ABORTED = 65;                         /* Operation aborted */
+    int INVALID_RESPONSE = 66;                /* Invalid response sent by vendor code */
+
     // Below is list of OEM specific error codes which can by used by OEMs in case they don't want to
     // reveal particular replacement for Generic failure
     int OEM_ERROR_1 = 501;
@@ -419,6 +421,8 @@
     int RIL_REQUEST_STOP_NETWORK_SCAN = 143;
     int RIL_REQUEST_GET_SLOT_STATUS = 144;
     int RIL_REQUEST_SET_LOGICAL_TO_PHYSICAL_SLOT_MAPPING = 145;
+    int RIL_REQUEST_START_KEEPALIVE = 146;
+    int RIL_REQUEST_STOP_KEEPALIVE = 147;
 
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
 
@@ -474,4 +478,5 @@
     int RIL_UNSOL_CARRIER_INFO_IMSI_ENCRYPTION = 1048;
     int RIL_UNSOL_NETWORK_SCAN_RESULT = 1049;
     int RIL_UNSOL_ICC_SLOT_STATUS = 1050;
+    int RIL_UNSOL_KEEPALIVE_STATUS = 1051;
 }
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index 9f8b3a8..c095438 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -837,6 +837,13 @@
     }
 
     /**
+     * Strip all the trailing 'F' characters of a string, e.g., an ICCID.
+     */
+    public static String stripTrailingFs(String s) {
+        return s == null ? null : s.replaceAll("(?i)f*$", "");
+    }
+
+    /**
      * Converts a character of [0-9a-aA-F] to its hex value in a byte. If the character is not a
      * hex number, 0 will be returned.
      */
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 2c60118..8d1a00b 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -20,6 +20,7 @@
 import android.content.pm.PackageManager;
 import android.net.IpConfiguration;
 import android.net.IpConfiguration.ProxySettings;
+import android.net.MacAddress;
 import android.net.ProxyInfo;
 import android.net.StaticIpConfiguration;
 import android.net.Uri;
@@ -54,8 +55,10 @@
     /** {@hide} */
     public static final String pskVarName = "psk";
     /** {@hide} */
+    @Deprecated
     public static final String[] wepKeyVarNames = { "wep_key0", "wep_key1", "wep_key2", "wep_key3" };
     /** {@hide} */
+    @Deprecated
     public static final String wepTxKeyIdxVarName = "wep_tx_keyidx";
     /** {@hide} */
     public static final String priorityVarName = "priority";
@@ -82,6 +85,9 @@
         /** WPA is not used; plaintext or static WEP could be used. */
         public static final int NONE = 0;
         /** WPA pre-shared key (requires {@code preSharedKey} to be specified). */
+        /** @deprecated Due to security and performance limitations, use of WPA-1 networks
+         * is discouraged. WPA-2 (RSN) should be used instead. */
+        @Deprecated
         public static final int WPA_PSK = 1;
         /** WPA using EAP authentication. Generally used with an external authentication server. */
         public static final int WPA_EAP = 2;
@@ -115,8 +121,8 @@
 
         public static final String varName = "key_mgmt";
 
-        public static final String[] strings = { "NONE", "WPA_PSK", "WPA_EAP", "IEEE8021X",
-                "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP" };
+        public static final String[] strings = { "NONE", /* deprecated */ "WPA_PSK", "WPA_EAP",
+                "IEEE8021X", "WPA2_PSK", "OSEN", "FT_PSK", "FT_EAP" };
     }
 
     /**
@@ -125,7 +131,10 @@
     public static class Protocol {
         private Protocol() { }
 
-        /** WPA/IEEE 802.11i/D3.0 */
+        /** WPA/IEEE 802.11i/D3.0
+         * @deprecated Due to security and performance limitations, use of WPA-1 networks
+         * is discouraged. WPA-2 (RSN) should be used instead. */
+        @Deprecated
         public static final int WPA = 0;
         /** WPA2/IEEE 802.11i */
         public static final int RSN = 1;
@@ -147,7 +156,10 @@
 
         /** Open System authentication (required for WPA/WPA2) */
         public static final int OPEN = 0;
-        /** Shared Key authentication (requires static WEP keys) */
+        /** Shared Key authentication (requires static WEP keys)
+         * @deprecated Due to security and performance limitations, use of WEP networks
+         * is discouraged. */
+        @Deprecated
         public static final int SHARED = 1;
         /** LEAP/Network EAP (only used with LEAP) */
         public static final int LEAP = 2;
@@ -165,7 +177,10 @@
 
         /** Use only Group keys (deprecated) */
         public static final int NONE = 0;
-        /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] */
+        /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0]
+         * @deprecated Due to security and performance limitations, use of WPA-1 networks
+         * is discouraged. WPA-2 (RSN) should be used instead. */
+        @Deprecated
         public static final int TKIP = 1;
         /** AES in Counter mode with CBC-MAC [RFC 3610, IEEE 802.11i/D7.0] */
         public static final int CCMP = 2;
@@ -187,9 +202,15 @@
     public static class GroupCipher {
         private GroupCipher() { }
 
-        /** WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11) */
+        /** WEP40 = WEP (Wired Equivalent Privacy) with 40-bit key (original 802.11)
+         * @deprecated Due to security and performance limitations, use of WEP networks
+         * is discouraged. */
+        @Deprecated
         public static final int WEP40 = 0;
-        /** WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key */
+        /** WEP104 = WEP (Wired Equivalent Privacy) with 104-bit key
+         * @deprecated Due to security and performance limitations, use of WEP networks
+         * is discouraged. */
+        @Deprecated
         public static final int WEP104 = 1;
         /** Temporal Key Integrity Protocol [IEEE 802.11i/D7.0] */
         public static final int TKIP = 2;
@@ -203,7 +224,8 @@
         public static final String varName = "group";
 
         public static final String[] strings =
-                { "WEP40", "WEP104", "TKIP", "CCMP", "GTK_NOT_USED" };
+                { /* deprecated */ "WEP40", /* deprecated */ "WEP104",
+                        "TKIP", "CCMP", "GTK_NOT_USED" };
     }
 
     /** Possible status of a network configuration. */
@@ -309,10 +331,16 @@
      * When the value of one of these keys is read, the actual key is
      * not returned, just a "*" if the key has a value, or the null
      * string otherwise.
+     * @deprecated Due to security and performance limitations, use of WEP networks
+     * is discouraged.
      */
+    @Deprecated
     public String[] wepKeys;
 
-    /** Default WEP key index, ranging from 0 to 3. */
+    /** Default WEP key index, ranging from 0 to 3.
+     * @deprecated Due to security and performance limitations, use of WEP networks
+     * is discouraged. */
+    @Deprecated
     public int wepTxKeyIndex;
 
     /**
@@ -852,6 +880,52 @@
     @SystemApi
     public int numAssociation;
 
+    /**
+     * @hide
+     * Randomized MAC address to use with this particular network
+     */
+    private MacAddress mRandomizedMacAddress;
+
+    /**
+     * @hide
+     * Checks if the given MAC address can be used for Connected Mac Randomization
+     * by verifying that it is non-null, unicast, and locally assigned.
+     * @param mac MacAddress to check
+     * @return true if mac is good to use
+     */
+    private boolean isValidMacAddressForRandomization(MacAddress mac) {
+        return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned();
+    }
+
+    /**
+     * @hide
+     * Returns Randomized MAC address to use with the network.
+     * If it is not set/valid, create a new randomized address.
+     */
+    public MacAddress getOrCreateRandomizedMacAddress() {
+        if (!isValidMacAddressForRandomization(mRandomizedMacAddress)) {
+            mRandomizedMacAddress = MacAddress.createRandomUnicastAddress();
+        }
+        return mRandomizedMacAddress;
+    }
+
+    /**
+     * @hide
+     * Returns MAC address set to be the local randomized MAC address.
+     * Does not guarantee that the returned address is valid for use.
+     */
+    public MacAddress getRandomizedMacAddress() {
+        return mRandomizedMacAddress;
+    }
+
+    /**
+     * @hide
+     * @param mac MacAddress to change into
+     */
+    public void setRandomizedMacAddress(MacAddress mac) {
+        mRandomizedMacAddress = mac;
+    }
+
     /** @hide
      * Boost given to RSSI on a home network for the purpose of calculating the score
      * This adds stickiness to home networks, as defined by:
@@ -2124,6 +2198,7 @@
             updateTime = source.updateTime;
             shared = source.shared;
             recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus());
+            mRandomizedMacAddress = source.mRandomizedMacAddress;
         }
     }
 
@@ -2191,6 +2266,7 @@
         dest.writeInt(shared ? 1 : 0);
         dest.writeString(mPasspointManagementObjectTree);
         dest.writeInt(recentFailure.getAssociationStatus());
+        dest.writeParcelable(mRandomizedMacAddress, flags);
     }
 
     /** Implement the Parcelable interface {@hide} */
@@ -2259,6 +2335,7 @@
                 config.shared = in.readInt() != 0;
                 config.mPasspointManagementObjectTree = in.readString();
                 config.recentFailure.setAssociationStatus(in.readInt());
+                config.mRandomizedMacAddress = in.readParcelable(null);
                 return config;
             }
 
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 50ae905..05dcb33 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -3593,26 +3593,6 @@
     }
 
     /**
-     * Deprecated
-     * Does nothing
-     * @hide
-     * @deprecated
-     */
-    public void setAllowScansWithTraffic(int enabled) {
-        return;
-    }
-
-    /**
-     * Deprecated
-     * returns value for 'disabled'
-     * @hide
-     * @deprecated
-     */
-    public int getAllowScansWithTraffic() {
-        return 0;
-    }
-
-    /**
      * Resets all wifi manager settings back to factory defaults.
      *
      * @hide
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 622dce6..e7377c1 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertFalse;
 
 import android.os.Parcel;
+import android.net.MacAddress;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 
 import org.junit.Before;
@@ -49,6 +50,7 @@
         String cookie = "C O.o |<IE";
         WifiConfiguration config = new WifiConfiguration();
         config.setPasspointManagementObjectTree(cookie);
+        MacAddress macBeforeParcel = config.getOrCreateRandomizedMacAddress();
         Parcel parcelW = Parcel.obtain();
         config.writeToParcel(parcelW, 0);
         byte[] bytes = parcelW.marshall();
@@ -59,8 +61,9 @@
         parcelR.setDataPosition(0);
         WifiConfiguration reconfig = WifiConfiguration.CREATOR.createFromParcel(parcelR);
 
-        // lacking a useful config.equals, check one field near the end.
+        // lacking a useful config.equals, check two fields near the end.
         assertEquals(cookie, reconfig.getMoTree());
+        assertEquals(macBeforeParcel, reconfig.getOrCreateRandomizedMacAddress());
 
         Parcel parcelWW = Parcel.obtain();
         reconfig.writeToParcel(parcelWW, 0);
@@ -169,4 +172,48 @@
 
         assertFalse(config.isOpenNetwork());
     }
+
+    @Test
+    public void testGetOrCreateRandomizedMacAddress_SavesAndReturnsSameAddress() {
+        WifiConfiguration config = new WifiConfiguration();
+        MacAddress firstMacAddress = config.getOrCreateRandomizedMacAddress();
+        MacAddress secondMacAddress = config.getOrCreateRandomizedMacAddress();
+
+        assertEquals(firstMacAddress, secondMacAddress);
+    }
+
+    @Test
+    public void testSetRandomizedMacAddress_ChangesSavedAddress() {
+        WifiConfiguration config = new WifiConfiguration();
+        MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress();
+        config.setRandomizedMacAddress(macToChangeInto);
+        MacAddress macAfterChange = config.getOrCreateRandomizedMacAddress();
+
+        assertEquals(macToChangeInto, macAfterChange);
+    }
+
+    @Test
+    public void testGetOrCreateRandomizedMacAddress_ReRandomizesInvalidAddress() {
+        WifiConfiguration config =  new WifiConfiguration();
+
+        MacAddress macAddressZeroes = MacAddress.ALL_ZEROS_ADDRESS;
+        MacAddress macAddressMulticast = MacAddress.fromString("03:ff:ff:ff:ff:ff");
+        MacAddress macAddressGlobal = MacAddress.fromString("fc:ff:ff:ff:ff:ff");
+
+        config.setRandomizedMacAddress(null);
+        MacAddress macAfterChange = config.getOrCreateRandomizedMacAddress();
+        assertFalse(macAfterChange.equals(null));
+
+        config.setRandomizedMacAddress(macAddressZeroes);
+        macAfterChange = config.getOrCreateRandomizedMacAddress();
+        assertFalse(macAfterChange.equals(macAddressZeroes));
+
+        config.setRandomizedMacAddress(macAddressMulticast);
+        macAfterChange = config.getOrCreateRandomizedMacAddress();
+        assertFalse(macAfterChange.equals(macAddressMulticast));
+
+        config.setRandomizedMacAddress(macAddressGlobal);
+        macAfterChange = config.getOrCreateRandomizedMacAddress();
+        assertFalse(macAfterChange.equals(macAddressGlobal));
+    }
 }