Merge "Remove color extraction from scrims" into qt-dev
diff --git a/Android.bp b/Android.bp
index 21054dd..6a85f62 100644
--- a/Android.bp
+++ b/Android.bp
@@ -161,6 +161,7 @@
         ":libcamera_client_framework_aidl",
         "core/java/android/hardware/IConsumerIrService.aidl",
         "core/java/android/hardware/ISerialManager.aidl",
+        "core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl",
         "core/java/android/hardware/biometrics/IBiometricEnabledOnKeyguardCallback.aidl",
         "core/java/android/hardware/biometrics/IBiometricService.aidl",
         "core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl",
diff --git a/api/current.txt b/api/current.txt
index 535fbbcf..4bbae3b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5335,6 +5335,7 @@
     field public static final String EXTRA_TITLE = "android.title";
     field public static final String EXTRA_TITLE_BIG = "android.title.big";
     field public static final int FLAG_AUTO_CANCEL = 16; // 0x10
+    field public static final int FLAG_BUBBLE = 4096; // 0x1000
     field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40
     field public static final int FLAG_GROUP_SUMMARY = 512; // 0x200
     field @Deprecated public static final int FLAG_HIGH_PRIORITY = 128; // 0x80
@@ -9515,7 +9516,7 @@
     method public static android.content.SyncAdapterType[] getSyncAdapterTypes();
     method public static boolean getSyncAutomatically(android.accounts.Account, String);
     method @Nullable public final String getType(@NonNull android.net.Uri);
-    method @NonNull public final android.content.ContentResolver.TypeInfo getTypeInfo(@NonNull String);
+    method @NonNull public final android.content.ContentResolver.MimeTypeInfo getTypeInfo(@NonNull String);
     method @Nullable public final android.net.Uri insert(@RequiresPermission.Write @NonNull android.net.Uri, @Nullable android.content.ContentValues);
     method public static boolean isSyncActive(android.accounts.Account, String);
     method public static boolean isSyncPending(android.accounts.Account, String);
@@ -9595,7 +9596,7 @@
     field public static final int SYNC_OBSERVER_TYPE_SETTINGS = 1; // 0x1
   }
 
-  public static final class ContentResolver.TypeInfo {
+  public static final class ContentResolver.MimeTypeInfo {
     method @NonNull public CharSequence getContentDescription();
     method @NonNull public android.graphics.drawable.Icon getIcon();
     method @NonNull public CharSequence getLabel();
@@ -38478,8 +38479,8 @@
 
   public final class MediaStore {
     ctor public MediaStore();
-    method @NonNull public static java.util.Set<java.lang.String> getAllVolumeNames(@NonNull android.content.Context);
     method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri);
+    method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
     method public static android.net.Uri getMediaScannerUri();
     method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
     method @NonNull public static String getVersion(@NonNull android.content.Context);
@@ -38523,6 +38524,7 @@
     field public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE = "android.media.still_image_camera_preview_service";
     field public static final String UNKNOWN_STRING = "<unknown>";
     field public static final String VOLUME_EXTERNAL = "external";
+    field public static final String VOLUME_EXTERNAL_PRIMARY = "external_primary";
     field public static final String VOLUME_INTERNAL = "internal";
   }
 
@@ -38537,6 +38539,7 @@
     field public static final String ALBUM_ID = "album_id";
     field public static final String ALBUM_KEY = "album_key";
     field public static final String ARTIST = "artist";
+    field public static final String ARTIST_ID = "artist_id";
     field public static final String FIRST_YEAR = "minyear";
     field public static final String LAST_YEAR = "maxyear";
     field public static final String NUMBER_OF_SONGS = "numsongs";
@@ -38770,6 +38773,7 @@
     field public static final String RELATIVE_PATH = "relative_path";
     field public static final String SIZE = "_size";
     field public static final String TITLE = "title";
+    field public static final String VOLUME_NAME = "volume_name";
     field public static final String WIDTH = "width";
   }
 
@@ -51672,7 +51676,7 @@
     method public void addOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
     method public void addOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener);
     method public void addOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener);
-    method public void addOnSystemGestureExclusionRectsChangedListener(java.util.function.Consumer<java.util.List<android.graphics.Rect>>);
+    method public void addOnSystemGestureExclusionRectsChangedListener(@NonNull java.util.function.Consumer<java.util.List<android.graphics.Rect>>);
     method public void addOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener);
     method public void addOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener);
     method public void addOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener);
@@ -51687,7 +51691,7 @@
     method public void removeOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener);
     method public void removeOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener);
     method public void removeOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener);
-    method public void removeOnSystemGestureExclusionRectsChangedListener(java.util.function.Consumer<java.util.List<android.graphics.Rect>>);
+    method public void removeOnSystemGestureExclusionRectsChangedListener(@NonNull java.util.function.Consumer<java.util.List<android.graphics.Rect>>);
     method public void removeOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener);
     method public void removeOnWindowAttachListener(android.view.ViewTreeObserver.OnWindowAttachListener);
     method public void removeOnWindowFocusChangeListener(android.view.ViewTreeObserver.OnWindowFocusChangeListener);
diff --git a/api/removed.txt b/api/removed.txt
index fe3e866..70ff50e 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -514,6 +514,7 @@
 
   public final class MediaStore {
     method @Deprecated @NonNull public static android.net.Uri createPending(@NonNull android.content.Context, @NonNull android.provider.MediaStore.PendingParams);
+    method @Deprecated @NonNull public static java.util.Set<java.lang.String> getAllVolumeNames(@NonNull android.content.Context);
     method @Deprecated @NonNull public static android.provider.MediaStore.PendingSession openPending(@NonNull android.content.Context, @NonNull android.net.Uri);
     method @Deprecated @NonNull public static android.net.Uri setIncludeTrashed(@NonNull android.net.Uri);
     method @Deprecated public static void trash(@NonNull android.content.Context, @NonNull android.net.Uri);
diff --git a/api/system-current.txt b/api/system-current.txt
index 76b8f66..f242343 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -553,7 +553,7 @@
   }
 
   public class NotificationManager {
-    method @NonNull public java.util.List<java.lang.String> getAllowedAssistantCapabilities();
+    method @NonNull public java.util.List<java.lang.String> getAllowedAssistantAdjustments();
     method @Nullable public android.content.ComponentName getAllowedNotificationAssistant();
     method public boolean isNotificationAssistantAccessGranted(@NonNull android.content.ComponentName);
     method public void setNotificationAssistantAccessGranted(@Nullable android.content.ComponentName, boolean);
@@ -6631,8 +6631,8 @@
     method public final void adjustNotification(@NonNull android.service.notification.Adjustment);
     method public final void adjustNotifications(@NonNull java.util.List<android.service.notification.Adjustment>);
     method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
+    method public void onAllowedAdjustmentsChanged();
     method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
-    method public void onCapabilitiesChanged();
     method public void onNotificationDirectReplied(@NonNull String);
     method @Nullable public abstract android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification);
     method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
diff --git a/api/test-current.txt b/api/test-current.txt
index 7121a54..c3215a6 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -14,6 +14,7 @@
     field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
     field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
+    field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
     field public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
@@ -328,9 +329,9 @@
   }
 
   public class NotificationManager {
-    method public void allowAssistantCapability(String);
-    method public void disallowAssistantCapability(String);
-    method @NonNull public java.util.List<java.lang.String> getAllowedAssistantCapabilities();
+    method public void allowAssistantAdjustment(String);
+    method public void disallowAssistantAdjustment(String);
+    method @NonNull public java.util.List<java.lang.String> getAllowedAssistantAdjustments();
     method @Nullable public android.content.ComponentName getAllowedNotificationAssistant();
     method public android.content.ComponentName getEffectsSuppressor();
     method public boolean isNotificationAssistantAccessGranted(@NonNull android.content.ComponentName);
@@ -2488,8 +2489,8 @@
     method public final void adjustNotification(@NonNull android.service.notification.Adjustment);
     method public final void adjustNotifications(@NonNull java.util.List<android.service.notification.Adjustment>);
     method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int);
+    method public void onAllowedAdjustmentsChanged();
     method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent);
-    method public void onCapabilitiesChanged();
     method public void onNotificationDirectReplied(@NonNull String);
     method @Nullable public abstract android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification);
     method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
@@ -2698,6 +2699,7 @@
   public class TelephonyManager {
     method public int checkCarrierPrivilegesForPackage(String);
     method public int getCarrierIdListVersion();
+    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag();
     method public android.util.Pair<java.lang.Integer,java.lang.Integer> getRadioHalVersion();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void refreshUiccProfile();
     method @Deprecated public void setCarrierTestOverride(String, String, String, String, String, String, String);
@@ -2880,31 +2882,6 @@
     method public void writeRawZigZag64(long);
   }
 
-  public final class ProtoInputStream extends android.util.proto.ProtoStream {
-    ctor public ProtoInputStream(java.io.InputStream, int);
-    ctor public ProtoInputStream(java.io.InputStream);
-    ctor public ProtoInputStream(byte[]);
-    method public int decodeZigZag32(int);
-    method public long decodeZigZag64(long);
-    method public String dumpDebugData();
-    method public void end(long);
-    method public int getFieldNumber();
-    method public int getOffset();
-    method public int getWireType();
-    method public boolean isNextField(long) throws java.io.IOException;
-    method public int nextField() throws java.io.IOException;
-    method public boolean readBoolean(long) throws java.io.IOException;
-    method public byte[] readBytes(long) throws java.io.IOException;
-    method public double readDouble(long) throws java.io.IOException;
-    method public float readFloat(long) throws java.io.IOException;
-    method public int readInt(long) throws java.io.IOException;
-    method public long readLong(long) throws java.io.IOException;
-    method public String readString(long) throws java.io.IOException;
-    method public void skip() throws java.io.IOException;
-    method public long start(long) throws java.io.IOException;
-    field public static final int NO_MORE_FIELDS = -1; // 0xffffffff
-  }
-
   public final class ProtoOutputStream extends android.util.proto.ProtoStream {
     ctor public ProtoOutputStream();
     ctor public ProtoOutputStream(int);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index f7608f5..82d177a 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -3251,13 +3251,12 @@
         INSTALLER_ROLLBACK_BOOT_TRIGGERED_FAILURE = 14;
         INSTALLER_ROLLBACK_SUCCESS = 15;
         INSTALLER_ROLLBACK_FAILURE = 16;
-        INSTALLER_ROLLBACK_CANCEL_STAGED_REMOVE_FROM_QUEUE = 17;
-        INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_INITIATED = 18;
-        INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_SUCCESS = 19;
-        INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_FAILURE = 20;
-        INSTALL_STAGED_CANCEL_REQUESTED = 21;
-        INSTALL_STAGED_CANCEL_SUCCESS = 22;
-        INSTALL_STAGED_CANCEL_FAILURE = 23;
+        INSTALLER_ROLLBACK_STAGED_CANCEL_REQUESTED = 17;
+        INSTALLER_ROLLBACK_STAGED_CANCEL_SUCCESS = 18;
+        INSTALLER_ROLLBACK_STAGED_CANCEL_FAILURE = 19;
+        INSTALL_STAGED_CANCEL_REQUESTED = 20;
+        INSTALL_STAGED_CANCEL_SUCCESS = 21;
+        INSTALL_STAGED_CANCEL_FAILURE = 22;
     }
     optional State state = 6;
     // Possible experiment ids for monitoring this push.
@@ -5748,13 +5747,12 @@
         INSTALLER_ROLLBACK_BOOT_TRIGGERED_FAILURE = 14;
         INSTALLER_ROLLBACK_SUCCESS = 15;
         INSTALLER_ROLLBACK_FAILURE = 16;
-        INSTALLER_ROLLBACK_CANCEL_STAGED_REMOVE_FROM_QUEUE = 17;
-        INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_INITIATED = 18;
-        INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_SUCCESS = 19;
-        INSTALLER_ROLLBACK_CANCEL_STAGED_DELETE_SESSION_FAILURE = 20;
-        INSTALL_STAGED_CANCEL_REQUESTED = 21;
-        INSTALL_STAGED_CANCEL_SUCCESS = 22;
-        INSTALL_STAGED_CANCEL_FAILURE = 23;
+        INSTALLER_ROLLBACK_STAGED_CANCEL_REQUESTED = 17;
+        INSTALLER_ROLLBACK_STAGED_CANCEL_SUCCESS = 18;
+        INSTALLER_ROLLBACK_STAGED_CANCEL_FAILURE = 19;
+        INSTALL_STAGED_CANCEL_REQUESTED = 20;
+        INSTALL_STAGED_CANCEL_SUCCESS = 21;
+        INSTALL_STAGED_CANCEL_FAILURE = 22;
     }
     optional Status status = 4;
 }
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 54fe65d..9079ace 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -132,6 +132,7 @@
 import android.widget.Toast;
 import android.widget.Toolbar;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
@@ -4904,6 +4905,17 @@
             mTaskDescription.setNavigationBarColor(navigationBarColor);
         }
 
+        final int targetSdk = getApplicationInfo().targetSdkVersion;
+        final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;
+        if (!targetPreQ) {
+            mTaskDescription.setEnsureStatusBarContrastWhenTransparent(a.getBoolean(
+                    R.styleable.ActivityTaskDescription_ensureStatusBarContrastWhenTransparent,
+                    false));
+            mTaskDescription.setEnsureNavigationBarContrastWhenTransparent(a.getBoolean(
+                    R.styleable.ActivityTaskDescription_ensureNavigationBarContrastWhenTransparent,
+                    true));
+        }
+
         a.recycle();
         setTaskDescription(mTaskDescription);
     }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 395c867..b80f781 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -986,6 +986,8 @@
         private int mColorBackground;
         private int mStatusBarColor;
         private int mNavigationBarColor;
+        private boolean mEnsureStatusBarContrastWhenTransparent;
+        private boolean mEnsureNavigationBarContrastWhenTransparent;
 
         /**
          * Creates the TaskDescription to the specified values.
@@ -998,7 +1000,7 @@
          */
         @Deprecated
         public TaskDescription(String label, Bitmap icon, int colorPrimary) {
-            this(label, icon, 0, null, colorPrimary, 0, 0, 0);
+            this(label, icon, 0, null, colorPrimary, 0, 0, 0, false, false);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1014,7 +1016,7 @@
          *                     opaque.
          */
         public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
-            this(label, null, iconRes, null, colorPrimary, 0, 0, 0);
+            this(label, null, iconRes, null, colorPrimary, 0, 0, 0, false, false);
             if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
                 throw new RuntimeException("A TaskDescription's primary color should be opaque");
             }
@@ -1029,7 +1031,7 @@
          */
         @Deprecated
         public TaskDescription(String label, Bitmap icon) {
-            this(label, icon, 0, null, 0, 0, 0, 0);
+            this(label, icon, 0, null, 0, 0, 0, 0, false, false);
         }
 
         /**
@@ -1040,7 +1042,7 @@
          *                activity.
          */
         public TaskDescription(String label, @DrawableRes int iconRes) {
-            this(label, null, iconRes, null, 0, 0, 0, 0);
+            this(label, null, iconRes, null, 0, 0, 0, 0, false, false);
         }
 
         /**
@@ -1049,19 +1051,21 @@
          * @param label A label and description of the current state of this activity.
          */
         public TaskDescription(String label) {
-            this(label, null, 0, null, 0, 0, 0, 0);
+            this(label, null, 0, null, 0, 0, 0, 0, false, false);
         }
 
         /**
          * Creates an empty TaskDescription.
          */
         public TaskDescription() {
-            this(null, null, 0, null, 0, 0, 0, 0);
+            this(null, null, 0, null, 0, 0, 0, 0, false, false);
         }
 
         /** @hide */
         public TaskDescription(String label, Bitmap bitmap, int iconRes, String iconFilename,
-                int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor) {
+                int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor,
+                boolean ensureStatusBarContrastWhenTransparent,
+                boolean ensureNavigationBarContrastWhenTransparent) {
             mLabel = label;
             mIcon = bitmap;
             mIconRes = iconRes;
@@ -1070,6 +1074,9 @@
             mColorBackground = colorBackground;
             mStatusBarColor = statusBarColor;
             mNavigationBarColor = navigationBarColor;
+            mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
+            mEnsureNavigationBarContrastWhenTransparent =
+                    ensureNavigationBarContrastWhenTransparent;
         }
 
         /**
@@ -1092,6 +1099,9 @@
             mColorBackground = other.mColorBackground;
             mStatusBarColor = other.mStatusBarColor;
             mNavigationBarColor = other.mNavigationBarColor;
+            mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
+            mEnsureNavigationBarContrastWhenTransparent =
+                    other.mEnsureNavigationBarContrastWhenTransparent;
         }
 
         /**
@@ -1114,6 +1124,9 @@
             if (other.mNavigationBarColor != 0) {
                 mNavigationBarColor = other.mNavigationBarColor;
             }
+            mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
+            mEnsureNavigationBarContrastWhenTransparent =
+                    other.mEnsureNavigationBarContrastWhenTransparent;
         }
 
         private TaskDescription(Parcel source) {
@@ -1272,6 +1285,37 @@
             return mNavigationBarColor;
         }
 
+        /**
+         * @hide
+         */
+        public boolean getEnsureStatusBarContrastWhenTransparent() {
+            return mEnsureStatusBarContrastWhenTransparent;
+        }
+
+        /**
+         * @hide
+         */
+        public void setEnsureStatusBarContrastWhenTransparent(
+                boolean ensureStatusBarContrastWhenTransparent) {
+            mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
+        }
+
+        /**
+         * @hide
+         */
+        public boolean getEnsureNavigationBarContrastWhenTransparent() {
+            return mEnsureNavigationBarContrastWhenTransparent;
+        }
+
+        /**
+         * @hide
+         */
+        public void setEnsureNavigationBarContrastWhenTransparent(
+                boolean ensureNavigationBarContrastWhenTransparent) {
+            mEnsureNavigationBarContrastWhenTransparent =
+                    ensureNavigationBarContrastWhenTransparent;
+        }
+
         /** @hide */
         public void saveToXml(XmlSerializer out) throws IOException {
             if (mLabel != null) {
@@ -1332,6 +1376,8 @@
             dest.writeInt(mColorBackground);
             dest.writeInt(mStatusBarColor);
             dest.writeInt(mNavigationBarColor);
+            dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
+            dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
             if (mIconFilename == null) {
                 dest.writeInt(0);
             } else {
@@ -1348,6 +1394,8 @@
             mColorBackground = source.readInt();
             mStatusBarColor = source.readInt();
             mNavigationBarColor = source.readInt();
+            mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
+            mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
             mIconFilename = source.readInt() > 0 ? source.readString() : null;
         }
 
@@ -1366,8 +1414,11 @@
             return "TaskDescription Label: " + mLabel + " Icon: " + mIcon +
                     " IconRes: " + mIconRes + " IconFilename: " + mIconFilename +
                     " colorPrimary: " + mColorPrimary + " colorBackground: " + mColorBackground +
-                    " statusBarColor: " + mColorBackground +
-                    " navigationBarColor: " + mNavigationBarColor;
+                    " statusBarColor: " + mStatusBarColor + (
+                    mEnsureStatusBarContrastWhenTransparent ? " (contrast when transparent)"
+                            : "") + " navigationBarColor: " + mNavigationBarColor + (
+                    mEnsureNavigationBarContrastWhenTransparent
+                            ? " (contrast when transparent)" : "");
         }
     }
 
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 7884872..b3c2429 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -70,9 +70,9 @@
     boolean areNotificationsEnabled(String pkg);
     int getPackageImportance(String pkg);
 
-    List<String> getAllowedAssistantCapabilities(String pkg);
-    void allowAssistantCapability(String adjustmentType);
-    void disallowAssistantCapability(String adjustmentType);
+    List<String> getAllowedAssistantAdjustments(String pkg);
+    void allowAssistantAdjustment(String adjustmentType);
+    void disallowAssistantAdjustment(String adjustmentType);
 
     boolean shouldHideSilentStatusIcons(String callingPkg);
     void setHideSilentStatusIcons(boolean hide);
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 0ab1a85..a90185c 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -618,9 +618,11 @@
     public static final int FLAG_CAN_COLORIZE = 0x00000800;
 
     /**
-     * Bit to be bitswised-ored into the {@link #flags} field that should be
-     * set if this notification can be shown as a bubble.
-     * @hide
+     * Bit to be bitswised-ored into the {@link #flags} field that should be set if this
+     * notification is showing as a bubble. This will be set by the system if it is determined
+     * that your notification is allowed to be a bubble.
+     *
+     * @see {@link Notification.Builder#setBubbleMetadata(BubbleMetadata)}
      */
     public static final int FLAG_BUBBLE = 0x00001000;
 
@@ -3578,9 +3580,9 @@
          * <p>This data will be ignored unless the notification is posted to a channel that
          * allows {@link NotificationChannel#canBubble() bubbles}.</p>
          *
-         * <b>Notifications with a valid and allowed bubble metadata will display in collapsed state
-         * outside of the notification shade on unlocked devices. When a user interacts with the
-         * collapsed state, the bubble intent will be invoked and displayed.</b>
+         * <p>Notifications allowed to bubble that have valid bubble metadata will display in
+         * collapsed state outside of the notification shade on unlocked devices. When a user
+         * interacts with the collapsed state, the bubble intent will be invoked and displayed.</p>
          */
         @NonNull
         public Builder setBubbleMetadata(@Nullable BubbleMetadata data) {
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index d54aca8..dd39376 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -1206,10 +1206,10 @@
      */
     @SystemApi
     @TestApi
-    public @NonNull @Adjustment.Keys List<String> getAllowedAssistantCapabilities() {
+    public @NonNull @Adjustment.Keys List<String> getAllowedAssistantAdjustments() {
         INotificationManager service = getService();
         try {
-            return service.getAllowedAssistantCapabilities(mContext.getOpPackageName());
+            return service.getAllowedAssistantAdjustments(mContext.getOpPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1219,10 +1219,10 @@
      * @hide
      */
     @TestApi
-    public void allowAssistantCapability(String capability) {
+    public void allowAssistantAdjustment(String capability) {
         INotificationManager service = getService();
         try {
-            service.allowAssistantCapability(capability);
+            service.allowAssistantAdjustment(capability);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1232,10 +1232,10 @@
      * @hide
      */
     @TestApi
-    public void disallowAssistantCapability(String capability) {
+    public void disallowAssistantAdjustment(String capability) {
         INotificationManager service = getService();
         try {
-            service.disallowAssistantCapability(capability);
+            service.disallowAssistantAdjustment(capability);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 791c551..00f1e43 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -3398,7 +3398,7 @@
      *
      * @param mimeType Valid, concrete MIME type.
      */
-    public final @NonNull TypeInfo getTypeInfo(@NonNull String mimeType) {
+    public final @NonNull MimeTypeInfo getTypeInfo(@NonNull String mimeType) {
         Objects.requireNonNull(mimeType);
         return MimeIconUtils.getTypeInfo(mimeType);
     }
@@ -3407,13 +3407,13 @@
      * Detailed description of a specific MIME type, including an icon and label
      * that describe the type.
      */
-    public static final class TypeInfo {
+    public static final class MimeTypeInfo {
         private final Icon mIcon;
         private final CharSequence mLabel;
         private final CharSequence mContentDescription;
 
         /** {@hide} */
-        public TypeInfo(@NonNull Icon icon, @NonNull CharSequence label,
+        public MimeTypeInfo(@NonNull Icon icon, @NonNull CharSequence label,
                 @NonNull CharSequence contentDescription) {
             mIcon = Objects.requireNonNull(icon);
             mLabel = Objects.requireNonNull(label);
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 7fe840c..a71f7d2 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -30,7 +30,6 @@
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.PackageManager.DeleteFlags;
 import android.content.pm.PackageManager.InstallReason;
@@ -504,12 +503,14 @@
      *
      * <p>Staged session is active iff:
      * <ul>
-     *     <li>It is committed.
-     *     <li>It is not applied.
-     *     <li>It is not failed.
+     *     <li>It is committed, i.e. {@link SessionInfo#isCommitted()} is {@code true}, and
+     *     <li>it is not applied, i.e. {@link SessionInfo#isStagedSessionApplied()} is {@code
+     *     false}, and
+     *     <li>it is not failed, i.e. {@link SessionInfo#isStagedSessionFailed()} is {@code false}.
      * </ul>
      *
-     * <p>In case of a multi-apk session, parent session will be returned.
+     * <p>In case of a multi-apk session, reasoning above is applied to the parent session, since
+     * that is the one that should been {@link Session#commit committed}.
      */
     public @Nullable SessionInfo getActiveStagedSession() {
         final List<SessionInfo> stagedSessions = getStagedSessions();
@@ -2307,7 +2308,8 @@
         }
 
         /**
-         * Whenever this session was committed.
+         * Returns {@code true} if {@link Session#commit(IntentSender)}} was called for this
+         * session.
          */
         public boolean isCommitted() {
             return isCommitted;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 2b23e7a..b2b4e0e 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -591,8 +591,6 @@
      */
     public interface Callback {
         boolean hasFeature(String feature);
-        String[] getOverlayPaths(String targetPackageName, String targetPath);
-        String[] getOverlayApks(String targetPackageName);
     }
 
     /**
@@ -609,14 +607,6 @@
         @Override public boolean hasFeature(String feature) {
             return mPm.hasSystemFeature(feature);
         }
-
-        @Override public String[] getOverlayPaths(String targetPackageName, String targetPath) {
-            return null;
-        }
-
-        @Override public String[] getOverlayApks(String targetPackageName) {
-            return null;
-        }
     }
 
     /**
@@ -1168,19 +1158,7 @@
             }
 
             final byte[] bytes = IoUtils.readFileAsByteArray(cacheFile.getAbsolutePath());
-            Package p = fromCacheEntry(bytes);
-            if (mCallback != null) {
-                String[] overlayApks = mCallback.getOverlayApks(p.packageName);
-                if (overlayApks != null && overlayApks.length > 0) {
-                    for (String overlayApk : overlayApks) {
-                        // If a static RRO is updated, return null.
-                        if (!isCacheUpToDate(new File(overlayApk), cacheFile)) {
-                            return null;
-                        }
-                    }
-                }
-            }
-            return p;
+            return fromCacheEntry(bytes);
         } catch (Throwable e) {
             Slog.w(TAG, "Error reading package cache: ", e);
 
@@ -1354,7 +1332,7 @@
             final Resources res = new Resources(assets, mMetrics, null);
 
             final String[] outError = new String[1];
-            final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
+            final Package pkg = parseBaseApk(res, parser, flags, outError);
             if (pkg == null) {
                 throw new PackageParserException(mParseError,
                         apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
@@ -1602,8 +1580,8 @@
         final String apkPath = fd != null ? debugPathName : apkFile.getAbsolutePath();
 
         XmlResourceParser parser = null;
+        ApkAssets apkAssets = null;
         try {
-            final ApkAssets apkAssets;
             try {
                 apkAssets = fd != null
                         ? ApkAssets.loadFromFd(fd, debugPathName, false, false)
@@ -1640,7 +1618,13 @@
                     "Failed to parse " + apkPath, e);
         } finally {
             IoUtils.closeQuietly(parser);
-            // TODO(b/72056911): Implement and call close() on ApkAssets.
+            if (apkAssets != null) {
+                try {
+                    apkAssets.close();
+                } catch (Throwable ignored) {
+                }
+            }
+            // TODO(b/72056911): Implement AutoCloseable on ApkAssets.
         }
     }
 
@@ -1911,7 +1895,6 @@
      * need to consider whether they should be supported by split APKs and child
      * packages.
      *
-     * @param apkPath The package apk file path
      * @param res The resources from which to resolve values
      * @param parser The manifest parser
      * @param flags Flags how to parse
@@ -1921,8 +1904,7 @@
      * @throws XmlPullParserException
      * @throws IOException
      */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,
+    private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
             String[] outError) throws XmlPullParserException, IOException {
         final String splitName;
         final String pkgName;
@@ -1942,15 +1924,6 @@
             return null;
         }
 
-        if (mCallback != null) {
-            String[] overlayPaths = mCallback.getOverlayPaths(pkgName, apkPath);
-            if (overlayPaths != null && overlayPaths.length > 0) {
-                for (String overlayPath : overlayPaths) {
-                    res.getAssets().addOverlayPath(overlayPath);
-                }
-            }
-        }
-
         final Package pkg = new Package(pkgName);
 
         TypedArray sa = res.obtainAttributes(parser,
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index dc1d052..69462ab 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -36,7 +36,9 @@
  */
 public final class ApkAssets {
     @GuardedBy("this") private final long mNativePtr;
-    @GuardedBy("this") private StringBlock mStringBlock;
+    @GuardedBy("this") private final StringBlock mStringBlock;
+
+    @GuardedBy("this") private boolean mOpen = true;
 
     /**
      * Creates a new ApkAssets instance from the given path on disk.
@@ -180,7 +182,20 @@
 
     @Override
     protected void finalize() throws Throwable {
-        nativeDestroy(mNativePtr);
+        close();
+    }
+
+    /**
+     * Closes this class and the contained {@link #mStringBlock}.
+     */
+    public void close() throws Throwable {
+        synchronized (this) {
+            if (mOpen) {
+                mOpen = false;
+                mStringBlock.close();
+                nativeDestroy(mNativePtr);
+            }
+        }
     }
 
     private static native long nativeLoad(
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index b5ec0f9..b7bc822 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -18,13 +18,34 @@
 
 import android.annotation.UnsupportedAppUsage;
 import android.graphics.Color;
-import android.text.*;
-import android.text.style.*;
-import android.util.Log;
-import android.util.SparseArray;
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Typeface;
+import android.text.Annotation;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.SpannedString;
+import android.text.TextPaint;
+import android.text.TextUtils;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.BulletSpan;
+import android.text.style.CharacterStyle;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.LineHeightSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TextAppearanceSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
 
 import java.util.Arrays;
 
@@ -40,8 +61,12 @@
     private final long mNative;
     private final boolean mUseSparse;
     private final boolean mOwnsNative;
+
     private CharSequence[] mStrings;
     private SparseArray<CharSequence> mSparseStrings;
+
+    @GuardedBy("this") private boolean mOpen = true;
+
     StyleIDs mStyleIDs = null;
 
     public StringBlock(byte[] data, boolean useSparse) {
@@ -141,12 +166,23 @@
         }
     }
 
+    @Override
     protected void finalize() throws Throwable {
         try {
             super.finalize();
         } finally {
-            if (mOwnsNative) {
-                nativeDestroy(mNative);
+            close();
+        }
+    }
+
+    public void close() throws Throwable {
+        synchronized (this) {
+            if (mOpen) {
+                mOpen = false;
+
+                if (mOwnsNative) {
+                    nativeDestroy(mNative);
+                }
             }
         }
     }
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 014bc24..3523e95 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -52,7 +52,7 @@
     private static final Pattern sLimitPattern =
             Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
     private static final Pattern sAggregationPattern = Pattern.compile(
-            "(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL)\\((.+)\\)");
+            "(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL|GROUP_CONCAT)\\((.+)\\)");
 
     private Map<String, String> mProjectionMap = null;
     private List<Pattern> mProjectionGreylist = null;
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index a696eeb..6c497d4 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -207,5 +207,22 @@
             Slog.w(TAG, "onConfirmDeviceCredentialError(): Service not connected");
         }
     }
+
+    /**
+     * TODO(b/123378871): Remove when moved.
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void registerCancellationCallback(IBiometricConfirmDeviceCredentialCallback callback) {
+        if (mService != null) {
+            try {
+                mService.registerCancellationCallback(callback);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "registerCancellationCallback(): Service not connected");
+        }
+    }
 }
 
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index 08035972..1142a07 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -82,6 +82,11 @@
      * @hide
      */
     public static final String KEY_ALLOW_DEVICE_CREDENTIAL = "allow_device_credential";
+    /**
+     * @hide
+     */
+    public static final String KEY_FROM_CONFIRM_DEVICE_CREDENTIAL
+            = "from_confirm_device_credential";
 
     /**
      * Error/help message will show for this amount of time.
@@ -271,6 +276,17 @@
         }
 
         /**
+         * TODO(123378871): Remove when moved.
+         * @return
+         * @hide
+         */
+        @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+        @NonNull public Builder setFromConfirmDeviceCredential() {
+            mBundle.putBoolean(KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, true);
+            return this;
+        }
+
+        /**
          * Creates a {@link BiometricPrompt}.
          * @return a {@link BiometricPrompt}
          * @throws IllegalArgumentException if any of the required fields are not set.
@@ -494,7 +510,8 @@
     public void authenticateUser(@NonNull CancellationSignal cancel,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AuthenticationCallback callback,
-            int userId) {
+            int userId,
+            IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) {
         if (cancel == null) {
             throw new IllegalArgumentException("Must supply a cancellation signal");
         }
@@ -504,7 +521,8 @@
         if (callback == null) {
             throw new IllegalArgumentException("Must supply a callback");
         }
-        authenticateInternal(null /* crypto */, cancel, executor, callback, userId);
+        authenticateInternal(null /* crypto */, cancel, executor, callback, userId,
+                confirmDeviceCredentialCallback);
     }
 
     /**
@@ -555,7 +573,8 @@
         if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)) {
             throw new IllegalArgumentException("Device credential not supported with crypto");
         }
-        authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
+        authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId(),
+                null /* confirmDeviceCredentialCallback */);
     }
 
     /**
@@ -597,7 +616,8 @@
         if (callback == null) {
             throw new IllegalArgumentException("Must supply a callback");
         }
-        authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId());
+        authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId(),
+                null /* confirmDeviceCredentialCallback */);
     }
 
     private void cancelAuthentication() {
@@ -614,7 +634,8 @@
             @NonNull CancellationSignal cancel,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AuthenticationCallback callback,
-            int userId) {
+            int userId,
+            IBiometricConfirmDeviceCredentialCallback confirmDeviceCredentialCallback) {
         try {
             if (cancel.isCanceled()) {
                 Log.w(TAG, "Authentication already canceled");
@@ -629,7 +650,7 @@
             final long sessionId = crypto != null ? crypto.getOpId() : 0;
             if (BiometricManager.hasBiometrics(mContext)) {
                 mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver,
-                        mContext.getOpPackageName(), mBundle);
+                        mContext.getOpPackageName(), mBundle, confirmDeviceCredentialCallback);
             } else {
                 mExecutor.execute(() -> {
                     callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT,
diff --git a/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl b/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl
new file mode 100644
index 0000000..8b35852
--- /dev/null
+++ b/core/java/android/hardware/biometrics/IBiometricConfirmDeviceCredentialCallback.aidl
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2019 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.biometrics;
+
+/**
+ * Communication channel between ConfirmDeviceCredential / ConfirmLock* and BiometricService.
+ * @hide
+ */
+interface IBiometricConfirmDeviceCredentialCallback {
+    // Invoked when authentication should be canceled.
+    oneway void cancel();
+}
\ No newline at end of file
diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl
index 4971911..90d4921 100644
--- a/core/java/android/hardware/biometrics/IBiometricService.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricService.aidl
@@ -17,6 +17,7 @@
 package android.hardware.biometrics;
 
 import android.os.Bundle;
+import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 
@@ -30,8 +31,10 @@
 interface IBiometricService {
     // Requests authentication. The service choose the appropriate biometric to use, and show
     // the corresponding BiometricDialog.
+    // TODO(b/123378871): Remove callback when moved.
     void authenticate(IBinder token, long sessionId, int userId,
-            IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle);
+            IBiometricServiceReceiver receiver, String opPackageName, in Bundle bundle,
+            IBiometricConfirmDeviceCredentialCallback callback);
 
     // Cancel authentication for the given sessionId
     void cancelAuthentication(IBinder token, String opPackageName);
@@ -59,4 +62,8 @@
     void onConfirmDeviceCredentialSuccess();
     // TODO(b/123378871): Remove when moved.
     void onConfirmDeviceCredentialError(int error, String message);
+    // TODO(b/123378871): Remove when moved.
+    // When ConfirmLock* is invoked from BiometricPrompt, it needs to register a callback so that
+    // it can receive the cancellation signal.
+    void registerCancellationCallback(IBiometricConfirmDeviceCredentialCallback callback);
 }
diff --git a/core/java/android/net/NetworkStack.java b/core/java/android/net/NetworkStack.java
index dbb894f..a46c410 100644
--- a/core/java/android/net/NetworkStack.java
+++ b/core/java/android/net/NetworkStack.java
@@ -15,9 +15,16 @@
  */
 package android.net;
 
+import static android.Manifest.permission.NETWORK_STACK;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
+import android.content.Context;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 /**
  *
  * Constants for client code communicating with the network stack service.
@@ -37,4 +44,52 @@
             "android.permission.MAINLINE_NETWORK_STACK";
 
     private NetworkStack() {}
+
+    /**
+     * If the NetworkStack, MAINLINE_NETWORK_STACK are not allowed for a particular process, throw a
+     * {@link SecurityException}.
+     *
+     * @param context {@link android.content.Context} for the process.
+     *
+     * @hide
+     */
+    public static void checkNetworkStackPermission(final @NonNull Context context) {
+        checkNetworkStackPermissionOr(context);
+    }
+
+    /**
+     * If the NetworkStack, MAINLINE_NETWORK_STACK or other specified permissions are not allowed
+     * for a particular process, throw a {@link SecurityException}.
+     *
+     * @param context {@link android.content.Context} for the process.
+     * @param otherPermissions The set of permissions that could be the candidate permissions , or
+     *                         empty string if none of other permissions needed.
+     * @hide
+     */
+    public static void checkNetworkStackPermissionOr(final @NonNull Context context,
+            final @NonNull String... otherPermissions) {
+        ArrayList<String> permissions = new ArrayList<String>(Arrays.asList(otherPermissions));
+        permissions.add(NETWORK_STACK);
+        permissions.add(PERMISSION_MAINLINE_NETWORK_STACK);
+        enforceAnyPermissionOf(context, permissions.toArray(new String[0]));
+    }
+
+    private static void enforceAnyPermissionOf(final @NonNull Context context,
+            final @NonNull String... permissions) {
+        if (!checkAnyPermissionOf(context, permissions)) {
+            throw new SecurityException("Requires one of the following permissions: "
+                + String.join(", ", permissions) + ".");
+        }
+    }
+
+    private static boolean checkAnyPermissionOf(final @NonNull Context context,
+            final @NonNull String... permissions) {
+        for (String permission : permissions) {
+            if (context.checkCallingOrSelfPermission(permission) == PERMISSION_GRANTED) {
+                return true;
+            }
+        }
+        return false;
+    }
+
 }
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 53503f4..e56b6e0 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -188,11 +188,16 @@
 
                     if (gpuDebugLayerApp != null && !gpuDebugLayerApp.isEmpty()) {
                         Log.i(TAG, "GPU debug layer app: " + gpuDebugLayerApp);
-                        final String paths = getDebugLayerAppPaths(pm, gpuDebugLayerApp);
-                        if (paths != null) {
-                            // Append the path so files placed in the app's base directory will
-                            // override the external path
-                            layerPaths += paths + ":";
+                        // If a colon is present, treat this as multiple apps, so Vulkan and GLES
+                        // layer apps can be provided at the same time.
+                        String[] layerApps = gpuDebugLayerApp.split(":");
+                        for (int i = 0; i < layerApps.length; i++) {
+                            String paths = getDebugLayerAppPaths(pm, layerApps[i]);
+                            if (paths != null) {
+                                // Append the path so files placed in the app's base directory will
+                                // override the external path
+                                layerPaths += paths + ":";
+                            }
                         }
                     }
 
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index bd70f23..ab19fd6 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -646,9 +646,14 @@
                 ZygoteConfig.USAP_POOL_ENABLED, USAP_POOL_ENABLED_DEFAULT);
 
         if (!propertyString.isEmpty()) {
-            mUsapPoolEnabled = Zygote.getConfigurationPropertyBoolean(
+            if (SystemProperties.get("dalvik.vm.boot-image", "").endsWith("apex.art")) {
+                // TODO(b/119800099): Tweak usap configuration in jitzygote mode.
+                mUsapPoolEnabled = false;
+            } else {
+                mUsapPoolEnabled = Zygote.getConfigurationPropertyBoolean(
                     ZygoteConfig.USAP_POOL_ENABLED,
                     Boolean.parseBoolean(USAP_POOL_ENABLED_DEFAULT));
+            }
         }
 
         boolean valueChanged = origVal != mUsapPoolEnabled;
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 075b650..080ff73 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -1130,7 +1130,7 @@
     public @NonNull StorageVolume getStorageVolume(@NonNull Uri uri) {
         final String volumeName = MediaStore.getVolumeName(uri);
         switch (volumeName) {
-            case MediaStore.VOLUME_EXTERNAL:
+            case MediaStore.VOLUME_EXTERNAL_PRIMARY:
                 return getPrimaryStorageVolume();
             default:
                 for (StorageVolume vol : getStorageVolumes()) {
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 225ecfa..6280600 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -265,8 +265,13 @@
     }
 
     /** {@hide} */
+    public static @Nullable String normalizeUuid(@Nullable String fsUuid) {
+        return fsUuid != null ? fsUuid.toLowerCase(Locale.US) : null;
+    }
+
+    /** {@hide} */
     public @Nullable String getNormalizedUuid() {
-        return mFsUuid != null ? mFsUuid.toLowerCase(Locale.US) : null;
+        return normalizeUuid(mFsUuid);
     }
 
     /**
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index bda6ed1..da19d59 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -102,20 +102,40 @@
     public static final @NonNull Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
 
     /**
-     * Volume name used for content on "internal" storage of device. This
-     * volume contains media distributed with the device, such as built-in
-     * ringtones and wallpapers.
+     * Synthetic volume name that provides a view of all content across the
+     * "internal" storage of the device.
+     * <p>
+     * This synthetic volume provides a merged view of all media distributed
+     * with the device, such as built-in ringtones and wallpapers.
+     * <p>
+     * Because this is a synthetic volume, you can't insert new content into
+     * this volume.
      */
     public static final String VOLUME_INTERNAL = "internal";
 
     /**
-     * Volume name used for content on "external" storage of device. This only
-     * includes media on the primary shared storage device; the contents of any
-     * secondary storage devices can be obtained using
-     * {@link #getAllVolumeNames(Context)}.
+     * Synthetic volume name that provides a view of all content across the
+     * "external" storage of the device.
+     * <p>
+     * This synthetic volume provides a merged view of all media across all
+     * currently attached external storage devices.
+     * <p>
+     * Because this is a synthetic volume, you can't insert new content into
+     * this volume. Instead, you can insert content into a specific storage
+     * volume obtained from {@link #getExternalVolumeNames(Context)}.
      */
     public static final String VOLUME_EXTERNAL = "external";
 
+    /**
+     * Specific volume name that represents the primary external storage device
+     * at {@link Environment#getExternalStorageDirectory()}.
+     * <p>
+     * This volume may not always be available, such as when the user has
+     * ejected the device. You can find a list of all specific volume names
+     * using {@link #getExternalVolumeNames(Context)}.
+     */
+    public static final String VOLUME_EXTERNAL_PRIMARY = "external_primary";
+
     /** {@hide} */
     public static final String SCAN_FILE_CALL = "scan_file";
     /** {@hide} */
@@ -1037,6 +1057,16 @@
         public static final String OWNER_PACKAGE_NAME = "owner_package_name";
 
         /**
+         * Volume name of the specific storage device where this media item is
+         * persisted. The value is typically one of the volume names returned
+         * from {@link MediaStore#getExternalVolumeNames(Context)}.
+         * <p>
+         * This is a read-only column that is automatically computed.
+         */
+        @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
+        public static final String VOLUME_NAME = "volume_name";
+
+        /**
          * Relative path of this media item within the storage device where it
          * is persisted. For example, an item stored at
          * {@code /storage/0000-0000/DCIM/Vacation/IMG1024.JPG} would have a
@@ -1408,7 +1438,7 @@
             final StorageVolume sv = sm.getStorageVolume(path);
             if (sv != null) {
                 if (sv.isPrimary()) {
-                    return VOLUME_EXTERNAL;
+                    return VOLUME_EXTERNAL_PRIMARY;
                 } else {
                     return checkArgumentVolumeName(sv.getNormalizedUuid());
                 }
@@ -1710,7 +1740,7 @@
                 String stringUrl = null;    /* value to be returned */
 
                 try {
-                    url = cr.insert(EXTERNAL_CONTENT_URI, values);
+                    url = cr.insert(getContentUri(VOLUME_EXTERNAL_PRIMARY), values);
 
                     if (source != null) {
                         try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(
@@ -2676,7 +2706,13 @@
             public static final String ALBUM = "album";
 
             /**
-             * The artist whose songs appear on this album
+             * The ID of the artist whose songs appear on this album.
+             */
+            @Column(value = Cursor.FIELD_TYPE_INTEGER, readOnly = true)
+            public static final String ARTIST_ID = "artist_id";
+
+            /**
+             * The name of the artist whose songs appear on this album.
              */
             @Column(value = Cursor.FIELD_TYPE_STRING, readOnly = true)
             public static final String ARTIST = "artist";
@@ -3218,22 +3254,29 @@
         }
     }
 
-    /**
-     * Return list of all volume names currently available. This includes a
-     * unique name for each shared storage device that is currently mounted.
-     * <p>
-     * Each name can be passed to APIs like
-     * {@link MediaStore.Images.Media#getContentUri(String)} to query media at
-     * that location.
-     */
+    /** @removed */
+    @Deprecated
     public static @NonNull Set<String> getAllVolumeNames(@NonNull Context context) {
+        return getExternalVolumeNames(context);
+    }
+
+    /**
+     * Return list of all specific volume names that make up
+     * {@link #VOLUME_EXTERNAL}. This includes a unique volume name for each
+     * shared storage device that is currently attached, which typically
+     * includes {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}.
+     * <p>
+     * Each specific volume name can be passed to APIs like
+     * {@link MediaStore.Images.Media#getContentUri(String)} to interact with
+     * media on that storage device.
+     */
+    public static @NonNull Set<String> getExternalVolumeNames(@NonNull Context context) {
         final StorageManager sm = context.getSystemService(StorageManager.class);
         final Set<String> volumeNames = new ArraySet<>();
-        volumeNames.add(VOLUME_INTERNAL);
         for (VolumeInfo vi : sm.getVolumes()) {
             if (vi.isVisibleForUser(UserHandle.myUserId()) && vi.isMountedReadable()) {
                 if (vi.isPrimary()) {
-                    volumeNames.add(VOLUME_EXTERNAL);
+                    volumeNames.add(VOLUME_EXTERNAL_PRIMARY);
                 } else {
                     volumeNames.add(vi.getNormalizedFsUuid());
                 }
@@ -3264,6 +3307,8 @@
             return volumeName;
         } else if (VOLUME_EXTERNAL.equals(volumeName)) {
             return volumeName;
+        } else if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName)) {
+            return volumeName;
         }
 
         // When not one of the well-known values above, it must be a hex UUID
@@ -3279,8 +3324,9 @@
     }
 
     /**
-     * Return path where the given volume is mounted. Not valid for
-     * {@link #VOLUME_INTERNAL}.
+     * Return path where the given specific volume is mounted. Not valid for
+     * {@link #VOLUME_INTERNAL} or {@link #VOLUME_EXTERNAL}, since those are
+     * broad collections that cover many paths.
      *
      * @hide
      */
@@ -3291,8 +3337,12 @@
             throw new IllegalArgumentException();
         }
 
-        if (VOLUME_EXTERNAL.equals(volumeName)) {
-            return Environment.getExternalStorageDirectory();
+        switch (volumeName) {
+            case VOLUME_INTERNAL:
+            case VOLUME_EXTERNAL:
+                throw new FileNotFoundException(volumeName + " has no associated path");
+            case VOLUME_EXTERNAL_PRIMARY:
+                return Environment.getExternalStorageDirectory();
         }
 
         final StorageManager sm = AppGlobals.getInitialApplication()
@@ -3322,23 +3372,31 @@
             throw new IllegalArgumentException();
         }
 
+        final Context context = AppGlobals.getInitialApplication();
+        final UserManager um = context.getSystemService(UserManager.class);
+
         final ArrayList<File> res = new ArrayList<>();
         if (VOLUME_INTERNAL.equals(volumeName)) {
-            addCanoncialFile(res, new File(Environment.getRootDirectory(), "media"));
-            addCanoncialFile(res, new File(Environment.getOemDirectory(), "media"));
-            addCanoncialFile(res, new File(Environment.getProductDirectory(), "media"));
+            addCanonicalFile(res, new File(Environment.getRootDirectory(), "media"));
+            addCanonicalFile(res, new File(Environment.getOemDirectory(), "media"));
+            addCanonicalFile(res, new File(Environment.getProductDirectory(), "media"));
+        } else if (VOLUME_EXTERNAL.equals(volumeName)) {
+            for (String exactVolume : getExternalVolumeNames(context)) {
+                addCanonicalFile(res, getVolumePath(exactVolume));
+            }
+            if (um.isDemoUser()) {
+                addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
+            }
         } else {
-            addCanoncialFile(res, getVolumePath(volumeName));
-            final UserManager um = AppGlobals.getInitialApplication()
-                    .getSystemService(UserManager.class);
-            if (VOLUME_EXTERNAL.equals(volumeName) && um.isDemoUser()) {
-                addCanoncialFile(res, Environment.getDataPreloadsMediaDirectory());
+            addCanonicalFile(res, getVolumePath(volumeName));
+            if (VOLUME_EXTERNAL_PRIMARY.equals(volumeName) && um.isDemoUser()) {
+                addCanonicalFile(res, Environment.getDataPreloadsMediaDirectory());
             }
         }
         return res;
     }
 
-    private static void addCanoncialFile(List<File> list, File file) {
+    private static void addCanonicalFile(List<File> list, File file) {
         try {
             list.add(file.getCanonicalFile());
         } catch (IOException e) {
@@ -3376,12 +3434,12 @@
      * <p>
      * No other assumptions should be made about the meaning of the version.
      * <p>
-     * This method returns the version for {@link MediaStore#VOLUME_EXTERNAL};
-     * to obtain a version for a different volume, use
-     * {@link #getVersion(Context, String)}.
+     * This method returns the version for
+     * {@link MediaStore#VOLUME_EXTERNAL_PRIMARY}; to obtain a version for a
+     * different volume, use {@link #getVersion(Context, String)}.
      */
     public static @NonNull String getVersion(@NonNull Context context) {
-        return getVersion(context, VOLUME_EXTERNAL);
+        return getVersion(context, VOLUME_EXTERNAL_PRIMARY);
     }
 
     /**
@@ -3395,7 +3453,7 @@
      *
      * @param volumeName specific volume to obtain an opaque version string for.
      *            Must be one of the values returned from
-     *            {@link #getAllVolumeNames(Context)}.
+     *            {@link #getExternalVolumeNames(Context)}.
      */
     public static @NonNull String getVersion(@NonNull Context context, @NonNull String volumeName) {
         final ContentResolver resolver = context.getContentResolver();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 6e89797..e3b2d89 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -955,6 +955,20 @@
             "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
 
     /**
+     * Activity Action: Open the advanced power usage details page of an associated app.
+     * <p>
+     * Input: Intent's data URI set with an application name, using the
+     * "package" schema (like "package:com.my.app")
+     * <p>
+     * Output: Nothing.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_VIEW_ADVANCED_POWER_USAGE_DETAIL =
+            "android.settings.VIEW_ADVANCED_POWER_USAGE_DETAIL";
+
+    /**
      * Activity Action: Show screen for controlling background data
      * restrictions for a particular application.
      * <p>
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index 8bb5f97..5977baf 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -53,5 +53,5 @@
     void onNotificationDirectReply(String key);
     void onSuggestedReplySent(String key, in CharSequence reply, int source);
     void onActionClicked(String key, in Notification.Action action, int source);
-    void onCapabilitiesChanged();
+    void onAllowedAdjustmentsChanged();
 }
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index b4fd397..cafeb87 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -220,10 +220,10 @@
     /**
      * Implement this to know when a user has changed which features of
      * their notifications the assistant can modify.
-     * <p> Query {@link NotificationManager#getAllowedAssistantCapabilities()} to see what
+     * <p> Query {@link NotificationManager#getAllowedAssistantAdjustments()} to see what
      * {@link Adjustment adjustments} you are currently allowed to make.</p>
      */
-    public void onCapabilitiesChanged() {
+    public void onAllowedAdjustmentsChanged() {
     }
 
     /**
@@ -361,8 +361,8 @@
         }
 
         @Override
-        public void onCapabilitiesChanged() {
-            mHandler.obtainMessage(MyHandler.MSG_ON_CAPABILITIES_CHANGED).sendToTarget();
+        public void onAllowedAdjustmentsChanged() {
+            mHandler.obtainMessage(MyHandler.MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED).sendToTarget();
         }
     }
 
@@ -374,7 +374,7 @@
         public static final int MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT = 5;
         public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6;
         public static final int MSG_ON_ACTION_INVOKED = 7;
-        public static final int MSG_ON_CAPABILITIES_CHANGED = 8;
+        public static final int MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED = 8;
 
         public MyHandler(Looper looper) {
             super(looper, null, false);
@@ -456,8 +456,8 @@
                     onActionInvoked(key, action, source);
                     break;
                 }
-                case MSG_ON_CAPABILITIES_CHANGED: {
-                    onCapabilitiesChanged();
+                case MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED: {
+                    onAllowedAdjustmentsChanged();
                     break;
                 }
             }
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 016f4aa..3ec21e3 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -16,6 +16,7 @@
 
 package android.service.notification;
 
+import android.annotation.CurrentTimeMillisLong;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SdkConstant;
@@ -1399,7 +1400,7 @@
         }
 
         @Override
-        public void onCapabilitiesChanged() {
+        public void onAllowedAdjustmentsChanged() {
             // no-op in the listener
         }
 
@@ -1680,6 +1681,7 @@
          *
          * @return the time of the last alerting behavior, in milliseconds.
          */
+        @CurrentTimeMillisLong
         public long getLastAudiblyAlertedMillis() {
             return mLastAudiblyAlertedMs;
         }
diff --git a/core/java/android/util/proto/ProtoInputStream.java b/core/java/android/util/proto/ProtoInputStream.java
index cd2b6ce..c290dff 100644
--- a/core/java/android/util/proto/ProtoInputStream.java
+++ b/core/java/android/util/proto/ProtoInputStream.java
@@ -16,8 +16,6 @@
 
 package android.util.proto;
 
-import android.annotation.TestApi;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.StandardCharsets;
@@ -64,7 +62,6 @@
  *
  * @hide
  */
-@TestApi
 public final class ProtoInputStream extends ProtoStream {
 
     public static final int NO_MORE_FIELDS = -1;
diff --git a/core/java/android/util/proto/TEST_MAPPING b/core/java/android/util/proto/TEST_MAPPING
new file mode 100644
index 0000000..cf9f077
--- /dev/null
+++ b/core/java/android/util/proto/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "ProtoInputStreamTests"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index f9b629c8..1fc7f0e 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -416,23 +416,8 @@
     }
 
     private void initPrecompiledViews() {
-        // Use the device config if enabled, otherwise default to the system property.
-        String usePrecompiledLayout = null;
-        try {
-            usePrecompiledLayout = DeviceConfig.getProperty(
-                    DeviceConfig.NAMESPACE_RUNTIME,
-                    USE_PRECOMPILED_LAYOUT);
-        } catch (Exception e) {
-          // May be caused by permission errors reading the property (i.e. instant apps).
-        }
+        // Precompiled layouts are not supported in this release.
         boolean enabled = false;
-        if (TextUtils.isEmpty(usePrecompiledLayout)) {
-            enabled = SystemProperties.getBoolean(
-                    USE_PRECOMPILED_LAYOUT,
-                    false);
-        } else {
-            enabled = Boolean.parseBoolean(usePrecompiledLayout);
-        }
         initPrecompiledViews(enabled);
     }
 
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 2896bd0..c50a3aa 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -932,7 +932,8 @@
      * @param listener listener to add
      * @see View#setSystemGestureExclusionRects(List)
      */
-    public void addOnSystemGestureExclusionRectsChangedListener(Consumer<List<Rect>> listener) {
+    public void addOnSystemGestureExclusionRectsChangedListener(
+            @NonNull Consumer<List<Rect>> listener) {
         checkIsAlive();
         if (mGestureExclusionListeners == null) {
             mGestureExclusionListeners = new CopyOnWriteArray<>();
@@ -945,7 +946,8 @@
      * @see #addOnSystemGestureExclusionRectsChangedListener(Consumer)
      * @see View#setSystemGestureExclusionRects(List)
      */
-    public void removeOnSystemGestureExclusionRectsChangedListener(Consumer<List<Rect>> listener) {
+    public void removeOnSystemGestureExclusionRectsChangedListener(
+            @NonNull Consumer<List<Rect>> listener) {
         checkIsAlive();
         if (mGestureExclusionListeners == null) {
             return;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 3544a87..a9463e9 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -2329,6 +2329,70 @@
         return 0;
     }
 
+    /**
+     * Sets whether the system should ensure that the status bar has enough
+     * contrast when a fully transparent background is requested.
+     *
+     * <p>If set to this value, the system will determine whether a scrim is necessary
+     * to ensure that the status bar has enough contrast with the contents of
+     * this app, and set an appropriate effective bar background color accordingly.
+     *
+     * <p>When the status bar color has a non-zero alpha value, the value of this
+     * property has no effect.
+     *
+     * @see android.R.attr#ensureStatusBarContrastWhenTransparent
+     * @hide pending API
+     */
+    public void setEnsureStatusBarContrastWhenTransparent(boolean ensureContrast) {
+    }
+
+    /**
+     * Returns whether the system is ensuring that the status bar has enough contrast when a
+     * fully transparent background is requested.
+     *
+     * <p>When the status bar color has a non-zero alpha value, the value of this
+     * property has no effect.
+     *
+     * @see android.R.attr#ensureStatusBarContrastWhenTransparent
+     * @return true, if the system is ensuring contrast, false otherwise.
+     * @hide pending API
+     */
+    public boolean isEnsureStatusBarContrastWhenTransparent() {
+        return false;
+    }
+
+    /**
+     * Sets whether the system should ensure that the navigation bar has enough
+     * contrast when a fully transparent background is requested.
+     *
+     * <p>If set to this value, the system will determine whether a scrim is necessary
+     * to ensure that the navigation bar has enough contrast with the contents of
+     * this app, and set an appropriate effective bar background color accordingly.
+     *
+     * <p>When the navigation bar color has a non-zero alpha value, the value of this
+     * property has no effect.
+     *
+     * @see android.R.attr#ensureNavigationBarContrastWhenTransparent
+     * @hide pending API
+     */
+    public void setEnsureNavigationBarContrastWhenTransparent(boolean ensureContrast) {
+    }
+
+    /**
+     * Returns whether the system is ensuring that the navigation bar has enough contrast when a
+     * fully transparent background is requested.
+     *
+     * <p>When the navigation bar color has a non-zero alpha value, the value of this
+     * property has no effect.
+     *
+     * @return true, if the system is ensuring contrast, false otherwise.
+     * @see android.R.attr#ensureNavigationBarContrastWhenTransparent
+     * @hide pending API
+     */
+    public boolean isEnsureNavigationBarContrastWhenTransparent() {
+        return false;
+    }
+
     /** @hide */
     public void setTheme(int resId) {
     }
diff --git a/core/java/android/view/contentcapture/ContentCaptureCondition.java b/core/java/android/view/contentcapture/ContentCaptureCondition.java
index cf171d7..6f9d4d3 100644
--- a/core/java/android/view/contentcapture/ContentCaptureCondition.java
+++ b/core/java/android/view/contentcapture/ContentCaptureCondition.java
@@ -54,7 +54,9 @@
      *
      * @param locusId id of the condition, as defined by
      * {@link ContentCaptureContext#getLocusId()}.
-     * @param flags either {@link ContentCaptureCondition#FLAG_IS_REGEX} or {@code 0}.
+     * @param flags either {@link ContentCaptureCondition#FLAG_IS_REGEX} (to use a regular
+     * expression match) or {@code 0} (in which case the {@code LocusId} must be an exact match of
+     * the {@code LocusId} used in the {@link ContentCaptureContext}).
      */
     public ContentCaptureCondition(@NonNull LocusId locusId, @Flags int flags) {
         this.mLocusId = Preconditions.checkNotNull(locusId);
diff --git a/core/java/android/view/textclassifier/ConversationAction.java b/core/java/android/view/textclassifier/ConversationAction.java
index f2d878a..b8cb7be 100644
--- a/core/java/android/view/textclassifier/ConversationAction.java
+++ b/core/java/android/view/textclassifier/ConversationAction.java
@@ -200,13 +200,11 @@
     /**
      * Returns the extended data related to this conversation action.
      *
-     * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
-     * prefer to hold a reference to the returned bundle rather than frequently calling this
-     * method.
+     * <p><b>NOTE: </b>Do not modify this bundle.
      */
     @NonNull
     public Bundle getExtras() {
-        return mExtras.deepCopy();
+        return mExtras;
     }
 
     /** Builder class to construct {@link ConversationAction}. */
diff --git a/core/java/android/view/textclassifier/ConversationActions.java b/core/java/android/view/textclassifier/ConversationActions.java
index dc75212..eddc672 100644
--- a/core/java/android/view/textclassifier/ConversationActions.java
+++ b/core/java/android/view/textclassifier/ConversationActions.java
@@ -214,13 +214,11 @@
         /**
          * Returns the extended data related to this conversation action.
          *
-         * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
-         * prefer to hold a reference to the returned bundle rather than frequently calling this
-         * method.
+         * <p><b>NOTE: </b>Do not modify this bundle.
          */
         @NonNull
         public Bundle getExtras() {
-            return mExtras.deepCopy();
+            return mExtras;
         }
 
         /** Builder class to construct a {@link Message} */
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 9ede8fb..4c4cb55 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -265,13 +265,11 @@
     /**
      * Returns the extended data.
      *
-     * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
-     * prefer to hold a reference to the returned bundle rather than frequently calling this
-     * method.
+     * <p><b>NOTE: </b>Do not modify this bundle.
      */
     @NonNull
     public Bundle getExtras() {
-        return mExtras.deepCopy();
+        return mExtras;
     }
 
     @Override
@@ -635,13 +633,11 @@
         /**
          * Returns the extended data.
          *
-         * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
-         * prefer to hold a reference to the returned bundle rather than frequently calling this
-         * method.
+         * <p><b>NOTE: </b>Do not modify this bundle.
          */
         @NonNull
         public Bundle getExtras() {
-            return mExtras.deepCopy();
+            return mExtras;
         }
 
         /**
diff --git a/core/java/android/view/textclassifier/TextLinks.java b/core/java/android/view/textclassifier/TextLinks.java
index cde27a0..c815f63 100644
--- a/core/java/android/view/textclassifier/TextLinks.java
+++ b/core/java/android/view/textclassifier/TextLinks.java
@@ -125,13 +125,11 @@
     /**
      * Returns the extended data.
      *
-     * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
-     * prefer to hold a reference to the returned bundle rather than frequently calling this
-     * method.
+     * <p><b>NOTE: </b>Do not modify this bundle.
      */
     @NonNull
     public Bundle getExtras() {
-        return mExtras.deepCopy();
+        return mExtras;
     }
 
     /**
@@ -413,13 +411,11 @@
         /**
          * Returns the extended data.
          *
-         * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
-         * prefer to hold a reference to the returned bundle rather than frequently calling this
-         * method.
+         * <p><b>NOTE: </b>Do not modify this bundle.
          */
         @NonNull
         public Bundle getExtras() {
-            return mExtras.deepCopy();
+            return mExtras;
         }
 
         /**
diff --git a/core/java/android/view/textclassifier/TextSelection.java b/core/java/android/view/textclassifier/TextSelection.java
index 5298939..e378e65 100644
--- a/core/java/android/view/textclassifier/TextSelection.java
+++ b/core/java/android/view/textclassifier/TextSelection.java
@@ -112,13 +112,11 @@
     /**
      * Returns the extended data.
      *
-     * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
-     * prefer to hold a reference to the returned bundle rather than frequently calling this
-     * method.
+     * <p><b>NOTE: </b>Do not modify this bundle.
      */
     @NonNull
     public Bundle getExtras() {
-        return mExtras.deepCopy();
+        return mExtras;
     }
 
     @Override
@@ -296,13 +294,11 @@
         /**
          * Returns the extended data.
          *
-         * <p><b>NOTE: </b>Each call to this method returns a new bundle copy so clients should
-         * prefer to hold a reference to the returned bundle rather than frequently calling this
-         * method.
+         * <p><b>NOTE: </b>Do not modify this bundle.
          */
         @NonNull
         public Bundle getExtras() {
-            return mExtras.deepCopy();
+            return mExtras;
         }
 
         /**
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 51303f7..eeca732 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2592,10 +2592,8 @@
 
             if (startType != lastStartType || rowPosition == getContentPreviewRowCount()) {
                 row.setBackground(mChooserRowLayer);
-                setVertPadding(row, 0, 0);
             } else {
                 row.setBackground(null);
-                setVertPadding(row, 0, 0);
             }
 
             int columnCount = holder.getColumnCount();
@@ -2642,10 +2640,6 @@
             }
         }
 
-        private void setVertPadding(ViewGroup row, int top, int bottom) {
-            row.setPadding(row.getPaddingLeft(), top, row.getPaddingRight(), bottom);
-        }
-
         int getFirstRowPosition(int row) {
             row -= getContentPreviewRowCount();
 
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 1c0030d..d945e13 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -38,6 +38,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
+
 import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL;
 
 import android.animation.Animator;
@@ -125,6 +126,8 @@
     // The height of a window which has not in DIP.
     private final static int DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP = 5;
 
+    private static final int SCRIM_LIGHT = 0x99ffffff; // 60% white
+
     public static final ColorViewAttributes STATUS_BAR_COLOR_VIEW_ATTRIBUTES =
             new ColorViewAttributes(SYSTEM_UI_FLAG_FULLSCREEN, FLAG_TRANSLUCENT_STATUS,
                     Gravity.TOP, Gravity.LEFT, Gravity.RIGHT,
@@ -1237,19 +1240,31 @@
 
     private int calculateStatusBarColor() {
         return calculateBarColor(mWindow.getAttributes().flags, FLAG_TRANSLUCENT_STATUS,
-                mSemiTransparentBarColor, mWindow.mStatusBarColor);
+                mSemiTransparentBarColor, mWindow.mStatusBarColor,
+                getWindowSystemUiVisibility(), SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+                mWindow.mEnsureStatusBarContrastWhenTransparent);
     }
 
     private int calculateNavigationBarColor() {
         return calculateBarColor(mWindow.getAttributes().flags, FLAG_TRANSLUCENT_NAVIGATION,
-                mSemiTransparentBarColor, mWindow.mNavigationBarColor);
+                mSemiTransparentBarColor, mWindow.mNavigationBarColor,
+                getWindowSystemUiVisibility(), SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                mWindow.mEnsureNavigationBarContrastWhenTransparent
+                        && getContext().getResources().getBoolean(R.bool.config_navBarNeedsScrim));
     }
 
     public static int calculateBarColor(int flags, int translucentFlag, int semiTransparentBarColor,
-            int barColor) {
-        return (flags & translucentFlag) != 0 ? semiTransparentBarColor
-                : (flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 ? barColor
-                : Color.BLACK;
+            int barColor, int sysuiVis, int lightSysuiFlag, boolean scrimTransparent) {
+        if ((flags & translucentFlag) != 0) {
+            return semiTransparentBarColor;
+        } else if ((flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
+            return Color.BLACK;
+        } else if (scrimTransparent && barColor == Color.TRANSPARENT) {
+            boolean light = (sysuiVis & lightSysuiFlag) != 0;
+            return light ? SCRIM_LIGHT : semiTransparentBarColor;
+        } else {
+            return barColor;
+        }
     }
 
     private int getCurrentColor(ColorViewState state) {
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 04559e4..16d6c52 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -50,6 +50,7 @@
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
 import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Parcel;
@@ -247,6 +248,9 @@
     private boolean mForcedStatusBarColor = false;
     private boolean mForcedNavigationBarColor = false;
 
+    boolean mEnsureStatusBarContrastWhenTransparent;
+    boolean mEnsureNavigationBarContrastWhenTransparent;
+
     @UnsupportedAppUsage
     private CharSequence mTitle = null;
 
@@ -2439,6 +2443,7 @@
         final boolean targetPreHoneycomb = targetSdk < android.os.Build.VERSION_CODES.HONEYCOMB;
         final boolean targetPreIcs = targetSdk < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH;
         final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
+        final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;
         final boolean targetHcNeedsOptions = context.getResources().getBoolean(
                 R.bool.target_honeycomb_needs_options_menu);
         final boolean noActionBar = !hasFeature(FEATURE_ACTION_BAR) || hasFeature(FEATURE_NO_TITLE);
@@ -2457,6 +2462,12 @@
             mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
                     0x00000000);
         }
+        if (!targetPreQ) {
+            mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
+                    R.styleable.Window_ensureStatusBarContrastWhenTransparent, false);
+            mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
+                    R.styleable.Window_ensureNavigationBarContrastWhenTransparent, true);
+        }
 
         WindowManager.LayoutParams params = getAttributes();
 
@@ -3845,6 +3856,32 @@
         return mNavigationBarDividerColor;
     }
 
+    @Override
+    public void setEnsureStatusBarContrastWhenTransparent(boolean ensureContrast) {
+        mEnsureStatusBarContrastWhenTransparent = ensureContrast;
+        if (mDecor != null) {
+            mDecor.updateColorViews(null, false /* animate */);
+        }
+    }
+
+    @Override
+    public boolean isEnsureStatusBarContrastWhenTransparent() {
+        return mEnsureStatusBarContrastWhenTransparent;
+    }
+
+    @Override
+    public void setEnsureNavigationBarContrastWhenTransparent(boolean ensureContrast) {
+        mEnsureNavigationBarContrastWhenTransparent = ensureContrast;
+        if (mDecor != null) {
+            mDecor.updateColorViews(null, false /* animate */);
+        }
+    }
+
+    @Override
+    public boolean isEnsureNavigationBarContrastWhenTransparent() {
+        return mEnsureNavigationBarContrastWhenTransparent;
+    }
+
     public void setIsStartingWindow(boolean isStartingWindow) {
         mIsStartingWindow = isStartingWindow;
     }
diff --git a/core/java/com/android/internal/util/MimeIconUtils.java b/core/java/com/android/internal/util/MimeIconUtils.java
index 0b5fa6d..8523b4e 100644
--- a/core/java/com/android/internal/util/MimeIconUtils.java
+++ b/core/java/com/android/internal/util/MimeIconUtils.java
@@ -18,7 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.ContentResolver.TypeInfo;
+import android.content.ContentResolver.MimeTypeInfo;
 import android.content.res.Resources;
 import android.graphics.drawable.Icon;
 import android.text.TextUtils;
@@ -34,9 +34,9 @@
 
 public class MimeIconUtils {
     @GuardedBy("sCache")
-    private static final ArrayMap<String, TypeInfo> sCache = new ArrayMap<>();
+    private static final ArrayMap<String, MimeTypeInfo> sCache = new ArrayMap<>();
 
-    private static TypeInfo buildTypeInfo(String mimeType, int iconId,
+    private static MimeTypeInfo buildTypeInfo(String mimeType, int iconId,
             int labelId, int extLabelId) {
         final Resources res = Resources.getSystem();
 
@@ -49,10 +49,10 @@
             label = res.getString(labelId);
         }
 
-        return new TypeInfo(Icon.createWithResource(res, iconId), label, label);
+        return new MimeTypeInfo(Icon.createWithResource(res, iconId), label, label);
     }
 
-    private static @Nullable TypeInfo buildTypeInfo(@NonNull String mimeType) {
+    private static @Nullable MimeTypeInfo buildTypeInfo(@NonNull String mimeType) {
         switch (mimeType) {
             case "inode/directory":
             case "vnd.android.document/directory":
@@ -222,7 +222,7 @@
         }
     }
 
-    private static @Nullable TypeInfo buildGenericTypeInfo(@NonNull String mimeType) {
+    private static @Nullable MimeTypeInfo buildGenericTypeInfo(@NonNull String mimeType) {
         // Look for partial matches
         if (mimeType.startsWith("audio/")) {
             return buildTypeInfo(mimeType, R.drawable.ic_doc_audio,
@@ -252,12 +252,12 @@
                 R.string.mime_type_generic, R.string.mime_type_generic_ext);
     }
 
-    public static @NonNull TypeInfo getTypeInfo(@NonNull String mimeType) {
+    public static @NonNull MimeTypeInfo getTypeInfo(@NonNull String mimeType) {
         // Normalize MIME type
         mimeType = mimeType.toLowerCase(Locale.US);
 
         synchronized (sCache) {
-            TypeInfo res = sCache.get(mimeType);
+            MimeTypeInfo res = sCache.get(mimeType);
             if (res == null) {
                 res = buildTypeInfo(mimeType);
                 sCache.put(mimeType, res);
diff --git a/core/jni/android_hardware_camera2_DngCreator.cpp b/core/jni/android_hardware_camera2_DngCreator.cpp
index 29051f1..1c247cb 100644
--- a/core/jni/android_hardware_camera2_DngCreator.cpp
+++ b/core/jni/android_hardware_camera2_DngCreator.cpp
@@ -19,6 +19,7 @@
 #include <inttypes.h>
 #include <string.h>
 #include <algorithm>
+#include <array>
 #include <memory>
 #include <vector>
 #include <cmath>
@@ -976,6 +977,153 @@
     return OK;
 }
 
+static void undistort(/*inout*/double& x, /*inout*/double& y,
+        const std::array<float, 6>& distortion,
+        const float cx, const float cy, const float f) {
+    double xp = (x - cx) / f;
+    double yp = (y - cy) / f;
+
+    double x2 = xp * xp;
+    double y2 = yp * yp;
+    double r2 = x2 + y2;
+    double xy2 = 2.0 * xp * yp;
+
+    const float k0 = distortion[0];
+    const float k1 = distortion[1];
+    const float k2 = distortion[2];
+    const float k3 = distortion[3];
+    const float p1 = distortion[4];
+    const float p2 = distortion[5];
+
+    double kr = k0 + ((k3 * r2 + k2) * r2 + k1) * r2;
+    double xpp = xp * kr + p1 * xy2 + p2 * (r2 + 2.0 * x2);
+    double ypp = yp * kr + p1 * (r2 + 2.0 * y2) + p2 * xy2;
+
+    x = xpp * f + cx;
+    y = ypp * f + cy;
+    return;
+}
+
+static inline bool unDistortWithinPreCorrArray(
+        double x, double y,
+        const std::array<float, 6>& distortion,
+        const float cx, const float cy, const float f,
+        int preCorrW, int preCorrH) {
+    undistort(x, y, distortion, cx, cy, f);
+    if (x < 0.0 || y < 0.0 || x > preCorrW - 1 || y > preCorrH - 1) {
+        return false;
+    }
+    return true;
+}
+
+static inline bool boxWithinPrecorrectionArray(
+        int left, int top, int right, int bottom,
+        const std::array<float, 6>& distortion,
+        const float& cx, const float& cy, const float& f,
+        const int& preCorrW, const int& preCorrH){
+    // Top row
+    if (!unDistortWithinPreCorrArray(left, top, distortion, cx, cy, f, preCorrW, preCorrH)) {
+        return false;
+    }
+
+    if (!unDistortWithinPreCorrArray(cx, top, distortion, cx, cy, f, preCorrW, preCorrH)) {
+        return false;
+    }
+
+    if (!unDistortWithinPreCorrArray(right, top, distortion, cx, cy, f, preCorrW, preCorrH)) {
+        return false;
+    }
+
+    // Middle row
+    if (!unDistortWithinPreCorrArray(left, cy, distortion, cx, cy, f, preCorrW, preCorrH)) {
+        return false;
+    }
+
+    if (!unDistortWithinPreCorrArray(right, cy, distortion, cx, cy, f, preCorrW, preCorrH)) {
+        return false;
+    }
+
+    // Bottom row
+    if (!unDistortWithinPreCorrArray(left, bottom, distortion, cx, cy, f, preCorrW, preCorrH)) {
+        return false;
+    }
+
+    if (!unDistortWithinPreCorrArray(cx, bottom, distortion, cx, cy, f, preCorrW, preCorrH)) {
+        return false;
+    }
+
+    if (!unDistortWithinPreCorrArray(right, bottom, distortion, cx, cy, f, preCorrW, preCorrH)) {
+        return false;
+    }
+    return true;
+}
+
+static inline bool scaledBoxWithinPrecorrectionArray(
+        double scale/*must be <= 1.0*/,
+        const std::array<float, 6>& distortion,
+        const float cx, const float cy, const float f,
+        const int preCorrW, const int preCorrH){
+
+    double left = cx * (1.0 - scale);
+    double right = (preCorrW - 1) * scale + cx * (1.0 - scale);
+    double top = cy * (1.0 - scale);
+    double bottom = (preCorrH - 1) * scale + cy * (1.0 - scale);
+
+    return boxWithinPrecorrectionArray(left, top, right, bottom,
+            distortion, cx, cy, f, preCorrW, preCorrH);
+}
+
+static status_t findPostCorrectionScale(
+        double stepSize, double minScale,
+        const std::array<float, 6>& distortion,
+        const float cx, const float cy, const float f,
+        const int preCorrW, const int preCorrH,
+        /*out*/ double* outScale) {
+    if (outScale == nullptr) {
+        ALOGE("%s: outScale must not be null", __FUNCTION__);
+        return BAD_VALUE;
+    }
+
+    for (double scale = 1.0; scale > minScale; scale -= stepSize) {
+        if (scaledBoxWithinPrecorrectionArray(
+                scale, distortion, cx, cy, f, preCorrW, preCorrH)) {
+            *outScale = scale;
+            return OK;
+        }
+    }
+    ALOGE("%s: cannot find cropping scale for lens distortion: stepSize %f, minScale %f",
+            __FUNCTION__, stepSize, minScale);
+    return BAD_VALUE;
+}
+
+// Apply a scale factor to distortion coefficients so that the image is zoomed out and all pixels
+// are sampled within the precorrection array
+static void normalizeLensDistortion(
+        /*inout*/std::array<float, 6>& distortion,
+        float cx, float cy, float f, int preCorrW, int preCorrH) {
+    ALOGV("%s: distortion [%f, %f, %f, %f, %f, %f], (cx,cy) (%f, %f), f %f, (W,H) (%d, %d)",
+            __FUNCTION__, distortion[0], distortion[1], distortion[2],
+            distortion[3], distortion[4], distortion[5],
+            cx, cy, f, preCorrW, preCorrH);
+
+    // Only update distortion coeffients if we can find a good bounding box
+    double scale = 1.0;
+    if (OK == findPostCorrectionScale(0.002, 0.5,
+            distortion, cx, cy, f, preCorrW, preCorrH,
+            /*out*/&scale)) {
+        ALOGV("%s: scaling distortion coefficients by %f", __FUNCTION__, scale);
+        // The formula:
+        // xc = xi * (k0 + k1*r^2 + k2*r^4 + k3*r^6) + k4 * (2*xi*yi) + k5 * (r^2 + 2*xi^2)
+        // To create effective zoom we want to replace xi by xi *m, yi by yi*m and r^2 by r^2*m^2
+        // Factor the extra m power terms into k0~k6
+        std::array<float, 6> scalePowers = {1, 3, 5, 7, 2, 2};
+        for (size_t i = 0; i < 6; i++) {
+            distortion[i] *= pow(scale, scalePowers[i]);
+        }
+    }
+    return;
+}
+
 // ----------------------------------------------------------------------------
 extern "C" {
 
@@ -1086,9 +1234,9 @@
         uint32_t pixHeight = static_cast<uint32_t>(pixelArrayEntry.data.i32[1]);
 
         if (!((imageWidth == preWidth && imageHeight == preHeight) ||
-            (imageWidth == pixWidth && imageHeight == pixHeight))) {
+                (imageWidth == pixWidth && imageHeight == pixHeight))) {
             jniThrowException(env, "java/lang/AssertionError",
-                    "Height and width of imate buffer did not match height and width of"
+                    "Height and width of image buffer did not match height and width of"
                     "either the preCorrectionActiveArraySize or the pixelArraySize.");
             return nullptr;
         }
@@ -1793,7 +1941,7 @@
         status_t err = OK;
 
         // Set up rectilinear distortion correction
-        float distortion[6] {1.f, 0.f, 0.f, 0.f, 0.f, 0.f};
+        std::array<float, 6> distortion = {1.f, 0.f, 0.f, 0.f, 0.f, 0.f};
         bool gotDistortion = false;
 
         camera_metadata_entry entry4 =
@@ -1810,6 +1958,19 @@
                     results.find(ANDROID_LENS_DISTORTION);
             if (entry3.count == 5) {
                 gotDistortion = true;
+
+
+                // Scale the distortion coefficients to create a zoom in warpped image so that all
+                // pixels are drawn within input image.
+                for (size_t i = 0; i < entry3.count; i++) {
+                    distortion[i+1] = entry3.data.f[i];
+                }
+
+                // TODO b/118690688: deal with the case where RAW size != preCorrSize
+                if (preWidth == imageWidth && preHeight == imageHeight) {
+                    normalizeLensDistortion(distortion, cx, cy, f, preWidth, preHeight);
+                }
+
                 float m_x = std::fmaxf(preWidth-1 - cx, cx);
                 float m_y = std::fmaxf(preHeight-1 - cy, cy);
                 float m_sq = m_x*m_x + m_y*m_y;
@@ -1831,7 +1992,7 @@
                     m / f
                 };
                 for (size_t i = 0; i < entry3.count; i++) {
-                    distortion[i+1] = convCoeff[i] * entry3.data.f[i];
+                    distortion[i+1] *= convCoeff[i];
                 }
             } else {
                 entry3 = results.find(ANDROID_LENS_RADIAL_DISTORTION);
@@ -1859,8 +2020,8 @@
                 }
             }
             if (gotDistortion) {
-                err = builder.addWarpRectilinearForMetadata(distortion, preWidth, preHeight, cx,
-                        cy);
+                err = builder.addWarpRectilinearForMetadata(
+                        distortion.data(), preWidth, preHeight, cx, cy);
                 if (err != OK) {
                     ALOGE("%s: Could not add distortion correction.", __FUNCTION__);
                     jniThrowRuntimeException(env, "failed to add distortion correction.");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b94eb16..cc3b3a4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1637,8 +1637,8 @@
         android:label="@string/permlab_bluetooth"
         android:protectionLevel="normal" />
 
-    <!-- @SystemApi Allows an application to suspend other apps, which will prevent the user
-         from using them until they are unsuspended.
+    <!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the
+         user from using them until they are unsuspended.
          @hide
     -->
     <permission android:name="android.permission.SUSPEND_APPS"
diff --git a/core/res/res/drawable/ic_bluetooth_share_icon.xml b/core/res/res/drawable/ic_bluetooth_share_icon.xml
index 2446402..2152af5 100644
--- a/core/res/res/drawable/ic_bluetooth_share_icon.xml
+++ b/core/res/res/drawable/ic_bluetooth_share_icon.xml
@@ -19,9 +19,9 @@
     android:height="24dp"
     android:viewportWidth="24"
     android:viewportHeight="24"
-    android:tint="@android:color/accent_device_default">
+    android:tint="@android:color/accent_device_default_light">
 
     <path
         android:fillColor="@android:color/white"
         android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L11,14.41V22h1l5.71-5.71L13.41,12L17.71,7.71z M13,5.83 l1.88,1.88L13,9.59V5.83z M14.88,16.29L13,18.17v-3.76L14.88,16.29z" />
-</vector>
\ No newline at end of file
+</vector>
diff --git a/core/res/res/values-night/colors.xml b/core/res/res/values-night/colors.xml
index 6bbd258..37e452d 100644
--- a/core/res/res/values-night/colors.xml
+++ b/core/res/res/values-night/colors.xml
@@ -28,4 +28,6 @@
 
     <!-- The background color of a notification card. -->
     <color name="notification_material_background_color">@color/black</color>
-</resources>
\ No newline at end of file
+
+    <color name="chooser_row_divider">@color/list_divider_color_dark</color>
+</resources>
diff --git a/core/res/res/values-night/themes_device_defaults.xml b/core/res/res/values-night/themes_device_defaults.xml
index e35b750..98f209d 100644
--- a/core/res/res/values-night/themes_device_defaults.xml
+++ b/core/res/res/values-night/themes_device_defaults.xml
@@ -71,4 +71,8 @@
     <style name="ThemeOverlay.DeviceDefault.Accent.DayNight"
            parent="@style/ThemeOverlay.DeviceDefault.Accent" />
 
+    <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.ResolverCommon">
+        <item name="windowLightNavigationBar">false</item>
+    </style>
+
 </resources>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9d48fe3..a510424 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2091,6 +2091,40 @@
              Corresponds to {@link android.view.Window#setNavigationBarDividerColor(int)}. -->
         <attr name="navigationBarDividerColor" format="color" />
 
+        <!-- Sets whether the system should ensure that the status bar has enough
+             contrast when a fully transparent background is requested.
+
+             <p>If set to this value, the system will determine whether a scrim is necessary
+             to ensure that the status bar has enough contrast with the contents of
+             this app, and set an appropriate effective bar background color accordingly.
+
+             <p>When the status bar color has a non-zero alpha value, the value of this
+             attribute has no effect.
+
+             <p>If the app does not target at least {@link android.os.Build.VERSION_CODES#Q Q},
+             this attribute is ignored.
+
+             @see android.view.Window#setEnsureStatusBarContrastWhenTransparent 
+             @hide pendingAPI -->
+        <attr name="ensureStatusBarContrastWhenTransparent" format="boolean" />
+
+        <!-- Sets whether the system should ensure that the navigation bar has enough
+             contrast when a fully transparent background is requested.
+
+             <p>If set to this value, the system will determine whether a scrim is necessary
+             to ensure that the navigation bar has enough contrast with the contents of
+             this app, and set an appropriate effective bar background color accordingly.
+
+             <p>When the navigation bar color has a non-zero alpha value, the value of this
+             attribute has no effect.
+
+             <p>If the app does not target at least {@link android.os.Build.VERSION_CODES#Q Q},
+             this attribute is ignored.
+
+             @see android.view.Window#setEnsureNavigationBarContrastWhenTransparent
+             @hide pendingApi -->
+        <attr name="ensureNavigationBarContrastWhenTransparent" format="boolean" />
+
         <!-- The duration, in milliseconds, of the window background fade duration
              when transitioning into or away from an Activity when called with an
              Activity Transition. Corresponds to
@@ -8980,6 +9014,10 @@
         <!-- @hide From Theme.navigationBarColor, used for the TaskDescription navigation bar
                    color. -->
         <attr name="navigationBarColor"/>
+        <!-- @hide From Window.ensureStatusBarContrastWhenTransparent -->
+        <attr name="ensureStatusBarContrastWhenTransparent"/>
+        <!-- @hide From Window.ensureNavigationBarContrastWhenTransparent -->
+        <attr name="ensureNavigationBarContrastWhenTransparent"/>
     </declare-styleable>
 
     <declare-styleable name="Shortcut">
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index e9b1bd3..ef26cd7 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -215,6 +215,5 @@
     <!-- Magnifier -->
     <color name="default_magnifier_color_overlay">#00FFFFFF</color>
 
-    <color name="chooser_row_divider">#1f000000</color>
-
+    <color name="chooser_row_divider">@color/list_divider_color_light</color>
 </resources>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 0163fc0..8fcdc7b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1031,6 +1031,9 @@
     <!-- Boolean indicating whether display white balance is supported. -->
     <bool name="config_displayWhiteBalanceAvailable">false</bool>
 
+    <!-- Boolean indicating whether display white balance should be enabled by default. -->
+    <bool name="config_displayWhiteBalanceEnabledDefault">false</bool>
+
     <!-- Minimum color temperature, in Kelvin, supported by display white balance. -->
     <integer name="config_displayWhiteBalanceColorTemperatureMin">4000</integer>
 
@@ -3251,6 +3254,10 @@
     <!-- Controls the size of the back gesture inset. -->
     <dimen name="config_backGestureInset">0dp</dimen>
 
+    <!-- Controls whether the navbar needs a scrim with
+         {@link Window#setEnsureNavigationBarContrastWhenTransparent}. -->
+    <bool name="config_navBarNeedsScrim">true</bool>
+
     <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
          These values are in DPs and will be converted to pixel sizes internally. -->
     <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">16x16</string>
diff --git a/core/res/res/values/dimens_car.xml b/core/res/res/values/dimens_car.xml
index a43f752..f22a91f 100644
--- a/core/res/res/values/dimens_car.xml
+++ b/core/res/res/values/dimens_car.xml
@@ -66,9 +66,10 @@
     <dimen name="car_padding_0">4dp</dimen>
     <dimen name="car_padding_1">8dp</dimen>
     <dimen name="car_padding_2">16dp</dimen>
-    <dimen name="car_padding_3">28dp</dimen>
+    <dimen name="car_padding_3">24dp</dimen>
     <dimen name="car_padding_4">32dp</dimen>
     <dimen name="car_padding_5">64dp</dimen>
+    <dimen name="car_padding_6">96dp</dimen>
 
     <!-- Radius -->
     <dimen name="car_radius_1">4dp</dimen>
@@ -121,6 +122,9 @@
     <!-- Dialog button end margin -->
     <dimen name="button_end_margin">@*android:dimen/car_padding_4</dimen>
 
+    <!-- Dialog top padding when there is no title -->
+    <dimen name="dialog_no_title_padding_top">@*android:dimen/car_padding_4</dimen>
+
     <!-- Dialog start margin for text view -->
     <dimen name="text_view_start_margin">@*android:dimen/car_keyline_1</dimen>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index fb72da5..8cdce6b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2848,6 +2848,7 @@
   <java-symbol type="integer" name="config_navBarInteractionMode" />
   <java-symbol type="bool" name="config_navBarCanMove" />
   <java-symbol type="bool" name="config_navBarTapThrough" />
+  <java-symbol type="bool" name="config_navBarNeedsScrim" />
   <java-symbol type="dimen" name="config_backGestureInset" />
   <java-symbol type="color" name="system_bar_background_semi_transparent" />
 
@@ -3150,7 +3151,6 @@
 
   <java-symbol type="bool" name="config_setColorTransformAccelerated" />
   <java-symbol type="bool" name="config_setColorTransformAcceleratedPerLayer" />
-  <java-symbol type="bool" name="config_displayWhiteBalanceAvailable" />
   <java-symbol type="bool" name="config_nightDisplayAvailable" />
   <java-symbol type="bool" name="config_allowDisablingAssistDisclosure" />
   <java-symbol type="integer" name="config_defaultNightDisplayAutoMode" />
@@ -3162,8 +3162,8 @@
   <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficients" />
   <java-symbol type="array" name="config_nightDisplayColorTemperatureCoefficientsNative" />
   <java-symbol type="array" name="config_availableColorModes" />
-
   <java-symbol type="bool" name="config_displayWhiteBalanceAvailable" />
+  <java-symbol type="bool" name="config_displayWhiteBalanceEnabledDefault" />
   <java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureMin" />
   <java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureMax" />
   <java-symbol type="integer" name="config_displayWhiteBalanceColorTemperatureDefault" />
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index 9f20ee6..78a8db42 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -1658,7 +1658,7 @@
     <style name="Theme.DeviceDefault.DayNight" parent="Theme.DeviceDefault.Light" />
 
     <!-- Theme used for the intent picker activity. -->
-    <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.DayNight">
+    <style name="Theme.DeviceDefault.ResolverCommon" parent="Theme.DeviceDefault.DayNight">
         <item name="windowEnterTransition">@empty</item>
         <item name="windowExitTransition">@empty</item>
         <item name="windowIsTranslucent">true</item>
@@ -1670,6 +1670,12 @@
         <item name="colorControlActivated">?attr/colorControlHighlight</item>
         <item name="listPreferredItemPaddingStart">?attr/dialogPreferredPadding</item>
         <item name="listPreferredItemPaddingEnd">?attr/dialogPreferredPadding</item>
+        <item name="navigationBarColor">?attr/colorBackgroundFloating</item>
+        <item name="navigationBarDividerColor">@color/chooser_row_divider</item>
+    </style>
+
+    <style name="Theme.DeviceDefault.Resolver" parent="Theme.DeviceDefault.ResolverCommon">
+        <item name="windowLightNavigationBar">true</item>
     </style>
 
     <!-- @hide DeviceDefault themes for the autofill FillUi -->
diff --git a/libs/hwui/tests/unit/CommonPoolTests.cpp b/libs/hwui/tests/unit/CommonPoolTests.cpp
index c564ed6..70a5f5a 100644
--- a/libs/hwui/tests/unit/CommonPoolTests.cpp
+++ b/libs/hwui/tests/unit/CommonPoolTests.cpp
@@ -135,4 +135,48 @@
     for (auto& f : futures) {
         f.get();
     }
+}
+
+class ObjectTracker {
+    static std::atomic_int sGlobalCount;
+
+public:
+    ObjectTracker() {
+        sGlobalCount++;
+    }
+    ObjectTracker(const ObjectTracker&) {
+        sGlobalCount++;
+    }
+    ObjectTracker(ObjectTracker&&) {
+        sGlobalCount++;
+    }
+    ~ObjectTracker() {
+        sGlobalCount--;
+    }
+
+    static int count() { return sGlobalCount.load(); }
+};
+
+std::atomic_int ObjectTracker::sGlobalCount{0};
+
+TEST(CommonPool, asyncLifecycleCheck) {
+    ASSERT_EQ(0, ObjectTracker::count());
+    {
+        ObjectTracker obj;
+        ASSERT_EQ(1, ObjectTracker::count());
+        EXPECT_LT(1, CommonPool::async([obj] { return ObjectTracker::count(); }).get());
+    }
+    CommonPool::waitForIdle();
+    ASSERT_EQ(0, ObjectTracker::count());
+}
+
+TEST(CommonPool, syncLifecycleCheck) {
+    ASSERT_EQ(0, ObjectTracker::count());
+    {
+        ObjectTracker obj;
+        ASSERT_EQ(1, ObjectTracker::count());
+        EXPECT_LT(1, CommonPool::runSync([obj] { return ObjectTracker::count(); }));
+    }
+    CommonPool::waitForIdle();
+    ASSERT_EQ(0, ObjectTracker::count());
 }
\ No newline at end of file
diff --git a/libs/hwui/thread/CommonPool.cpp b/libs/hwui/thread/CommonPool.cpp
index 7f94a15..d011bdf 100644
--- a/libs/hwui/thread/CommonPool.cpp
+++ b/libs/hwui/thread/CommonPool.cpp
@@ -49,9 +49,13 @@
     }
 }
 
-void CommonPool::post(Task&& task) {
+CommonPool& CommonPool::instance() {
     static CommonPool pool;
-    pool.enqueue(std::move(task));
+    return pool;
+}
+
+void CommonPool::post(Task&& task) {
+    instance().enqueue(std::move(task));
 }
 
 void CommonPool::enqueue(Task&& task) {
@@ -86,5 +90,18 @@
     }
 }
 
+void CommonPool::waitForIdle() {
+    instance().doWaitForIdle();
+}
+
+void CommonPool::doWaitForIdle() {
+    std::unique_lock lock(mLock);
+    while (mWaitingThreads != THREAD_COUNT) {
+        lock.unlock();
+        usleep(100);
+        lock.lock();
+    }
+}
+
 }  // namespace uirenderer
 }  // namespace android
\ No newline at end of file
diff --git a/libs/hwui/thread/CommonPool.h b/libs/hwui/thread/CommonPool.h
index aef2990..7603eee 100644
--- a/libs/hwui/thread/CommonPool.h
+++ b/libs/hwui/thread/CommonPool.h
@@ -57,11 +57,13 @@
         mHead = newHead;
     }
 
-    constexpr T&& pop() {
+    constexpr T pop() {
         LOG_ALWAYS_FATAL_IF(mTail == mHead, "empty");
         int index = mTail;
         mTail = (mTail + 1) % SIZE;
-        return std::move(mBuffer[index]);
+        T ret = std::move(mBuffer[index]);
+        mBuffer[index] = nullptr;
+        return ret;
     }
 
 private:
@@ -95,11 +97,17 @@
         return task.get_future().get();
     };
 
+    // For testing purposes only, blocks until all worker threads are parked.
+    static void waitForIdle();
+
 private:
+    static CommonPool& instance();
+
     CommonPool();
     ~CommonPool() {}
 
     void enqueue(Task&&);
+    void doWaitForIdle();
 
     void workerLoop();
 
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index d14116f..39740bd 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -60,6 +60,9 @@
 }
 
 sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) {
+    if (dataspace == HAL_DATASPACE_UNKNOWN) {
+        return SkColorSpace::MakeSRGB();
+    }
 
     skcms_Matrix3x3 gamut;
     switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
diff --git a/media/Android.bp b/media/Android.bp
index 3480181..8746046 100644
--- a/media/Android.bp
+++ b/media/Android.bp
@@ -24,6 +24,10 @@
         "mediaplayer2-protos",
     ],
 
+    permitted_packages: [
+        "android.media",
+    ],
+
     installable: true,
 
     // Make sure that the implementaion only relies on SDK or system APIs.
diff --git a/media/OWNERS b/media/OWNERS
index 72c8952..a33a990 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -1,3 +1,4 @@
+andrewlewis@google.com
 chz@google.com
 dwkang@google.com
 elaurent@google.com
diff --git a/media/apex/java/android/media/MediaPlayer2.java b/media/apex/java/android/media/MediaPlayer2.java
index 87035da..72c18f6 100644
--- a/media/apex/java/android/media/MediaPlayer2.java
+++ b/media/apex/java/android/media/MediaPlayer2.java
@@ -31,6 +31,7 @@
 import android.media.MediaPlayer2.DrmInfo;
 import android.media.MediaPlayer2Proto.PlayerMessage;
 import android.media.MediaPlayer2Proto.Value;
+import android.media.protobuf.InvalidProtocolBufferException;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -46,7 +47,6 @@
 import android.view.SurfaceHolder;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.media.protobuf.InvalidProtocolBufferException;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -546,7 +546,7 @@
             @Override
             void process() {
                 if (getState() == PLAYER_STATE_PLAYING) {
-                    pause();
+                    native_pause();
                 }
                 playNextDataSource();
             }
diff --git a/media/proto/jarjar-rules.txt b/media/proto/jarjar-rules.txt
index bfb0b27..e73f86d 100644
--- a/media/proto/jarjar-rules.txt
+++ b/media/proto/jarjar-rules.txt
@@ -1,2 +1,2 @@
-rule com.google.protobuf.** com.android.media.protobuf.@1
+rule com.google.protobuf.** android.media.protobuf.@1
 
diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml
index a528636..355bdd8 100644
--- a/packages/CaptivePortalLogin/AndroidManifest.xml
+++ b/packages/CaptivePortalLogin/AndroidManifest.xml
@@ -18,7 +18,7 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.captiveportallogin"
-    android:versionCode="200000000"
+    android:versionCode="210000000"
     android:versionName="Q-initial">
 
     <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
index 0a20eaa..a371a1d8 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButton.java
@@ -21,6 +21,7 @@
 import android.content.res.TypedArray;
 import android.os.UserHandle;
 import android.util.AttributeSet;
+import android.view.Display;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -203,4 +204,16 @@
             mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : GONE);
         }
     }
+
+    /**
+     * @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on
+     *         a display.
+     */
+    public int getDisplayId() {
+        Display display = getDisplay();
+        if (display == null) {
+            return Display.INVALID_DISPLAY;
+        }
+        return display.getDisplayId();
+    }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java
index 7811a1c..d20038d 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarFacetButtonController.java
@@ -22,10 +22,13 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.view.Display;
+import android.util.Log;
 import android.view.View;
+import android.view.ViewGroup;
 
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 
@@ -40,15 +43,16 @@
 @Singleton
 public class CarFacetButtonController {
 
-    protected HashMap<String, CarFacetButton> mButtonsByCategory = new HashMap<>();
-    protected HashMap<String, CarFacetButton> mButtonsByPackage = new HashMap<>();
-    protected HashMap<String, CarFacetButton> mButtonsByComponentName = new HashMap<>();
-    protected CarFacetButton mSelectedFacetButton;
+    protected ButtonMap mButtonsByCategory = new ButtonMap();
+    protected ButtonMap mButtonsByPackage = new ButtonMap();
+    protected ButtonMap mButtonsByComponentName = new ButtonMap();
+    protected HashSet<CarFacetButton> mSelectedFacetButtons;
     protected Context mContext;
 
     @Inject
     public CarFacetButtonController(Context context) {
         mContext = context;
+        mSelectedFacetButtons = new HashSet<>();
     }
 
     /**
@@ -59,27 +63,40 @@
     public void addFacetButton(CarFacetButton facetButton) {
         String[] categories = facetButton.getCategories();
         for (int i = 0; i < categories.length; i++) {
-            mButtonsByCategory.put(categories[i], facetButton);
+            mButtonsByCategory.add(categories[i], facetButton);
         }
 
         String[] facetPackages = facetButton.getFacetPackages();
         for (int i = 0; i < facetPackages.length; i++) {
-            mButtonsByPackage.put(facetPackages[i], facetButton);
+            mButtonsByPackage.add(facetPackages[i], facetButton);
         }
         String[] componentNames = facetButton.getComponentName();
         for (int i = 0; i < componentNames.length; i++) {
-            mButtonsByComponentName.put(componentNames[i], facetButton);
+            mButtonsByComponentName.add(componentNames[i], facetButton);
         }
-        // Using the following as a default button for display id info it's not
-        // attached to a screen at this point so it can't be extracted here.
-        mSelectedFacetButton = facetButton;
     }
 
     public void removeAll() {
         mButtonsByCategory.clear();
         mButtonsByPackage.clear();
         mButtonsByComponentName.clear();
-        mSelectedFacetButton = null;
+        mSelectedFacetButtons.clear();
+    }
+
+    /**
+     * Iterate through a view looking for CarFacetButtons and adding them to the controller if found
+     *
+     * @param v the View that may contain CarFacetButtons
+     */
+    public void addAllFacetButtons(View v) {
+        if (v instanceof CarFacetButton) {
+            addFacetButton((CarFacetButton) v);
+        } else if (v instanceof ViewGroup) {
+            ViewGroup viewGroup = (ViewGroup) v;
+            for (int i = 0; i < viewGroup.getChildCount(); i++) {
+                addAllFacetButtons(viewGroup.getChildAt(i));
+            }
+        }
     }
 
     /**
@@ -94,12 +111,10 @@
      * @param stackInfoList of the currently running application
      */
     public void taskChanged(List<ActivityManager.StackInfo> stackInfoList) {
-        int displayId = getDisplayId();
         ActivityManager.StackInfo validStackInfo = null;
-        for (ActivityManager.StackInfo stackInfo : stackInfoList) {
-            // If the display id is unknown or it matches the stack, it's valid for use
-            if ((displayId == -1 || displayId == stackInfo.displayId)
-                    && stackInfo.topActivity != null) {
+        for (ActivityManager.StackInfo stackInfo :stackInfoList) {
+            // Find the first stack info with a topActivity
+            if (stackInfo.topActivity != null) {
                 validStackInfo = stackInfo;
                 break;
             }
@@ -110,12 +125,20 @@
             return;
         }
 
-        if (mSelectedFacetButton != null) {
-            mSelectedFacetButton.setSelected(false);
+        if (mSelectedFacetButtons != null) {
+            Iterator<CarFacetButton> iterator = mSelectedFacetButtons.iterator();
+            while(iterator.hasNext()) {
+                CarFacetButton carFacetButton = iterator.next();
+                if (carFacetButton.getDisplayId() == validStackInfo.displayId) {
+                    carFacetButton.setSelected(false);
+                    iterator.remove();
+                }
+            }
         }
 
         String packageName = validStackInfo.topActivity.getPackageName();
-        CarFacetButton facetButton = findFacetButtongByComponentName(validStackInfo.topActivity);
+        HashSet<CarFacetButton> facetButton =
+                findFacetButtonByComponentName(validStackInfo.topActivity);
         if (facetButton == null) {
             facetButton = mButtonsByPackage.get(packageName);
         }
@@ -127,26 +150,21 @@
             }
         }
 
-        if (facetButton != null && facetButton.getVisibility() == View.VISIBLE) {
-            facetButton.setSelected(true);
-            mSelectedFacetButton = facetButton;
-        }
-
-    }
-
-    private int getDisplayId() {
-        if (mSelectedFacetButton != null) {
-            Display display = mSelectedFacetButton.getDisplay();
-            if (display != null) {
-                return display.getDisplayId();
+        if (facetButton != null) {
+            for (CarFacetButton carFacetButton : facetButton) {
+                if (carFacetButton.getDisplayId() == validStackInfo.displayId) {
+                    carFacetButton.setSelected(true);
+                    mSelectedFacetButtons.add(carFacetButton);
+                }
             }
         }
-        return -1;
+
     }
 
-    private CarFacetButton findFacetButtongByComponentName(ComponentName componentName) {
-        CarFacetButton button = mButtonsByComponentName.get(componentName.flattenToShortString());
-        return (button != null) ? button :
+    private HashSet<CarFacetButton> findFacetButtonByComponentName(ComponentName componentName) {
+        HashSet<CarFacetButton> buttons =
+                mButtonsByComponentName.get(componentName.flattenToShortString());
+        return (buttons != null) ? buttons :
                 mButtonsByComponentName.get(componentName.flattenToString());
     }
 
@@ -168,4 +186,18 @@
         }
         return null;
     }
+
+    // simple multi-map
+    private static class ButtonMap extends HashMap<String, HashSet<CarFacetButton>> {
+
+        public boolean add(String key, CarFacetButton value) {
+            if (containsKey(key)) {
+                return get(key).add(value);
+            }
+            HashSet<CarFacetButton> set = new HashSet<>();
+            set.add(value);
+            put(key, set);
+            return true;
+        }
+    }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 9bcc1ab..44e8874 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -187,11 +187,13 @@
         if (mIsKeyguard) {
             updateNavBarForKeyguardContent();
         }
+        // CarFacetButtonController was reset therefore we need to re-add the status bar elements
+        // to the controller.
+        mCarFacetButtonController.addAllFacetButtons(mStatusBarWindow);
     }
 
     private void addTemperatureViewToController(View v) {
         if (v instanceof TemperatureView) {
-            Log.d(TAG, "addTemperatureViewToController: found ");
             mHvacController.addHvacTextView((TemperatureView) v);
         } else if (v instanceof ViewGroup) {
             ViewGroup viewGroup = (ViewGroup) v;
diff --git a/packages/ExtServices/AndroidManifest.xml b/packages/ExtServices/AndroidManifest.xml
index 45a59a3..73bfd30 100644
--- a/packages/ExtServices/AndroidManifest.xml
+++ b/packages/ExtServices/AndroidManifest.xml
@@ -17,7 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     package="android.ext.services"
-    android:versionCode="200000000"
+    android:versionCode="210000000"
     android:versionName="1"
     coreApp="true">
 
diff --git a/packages/NetworkStack/AndroidManifest.xml b/packages/NetworkStack/AndroidManifest.xml
index b4588e0..ac05c44 100644
--- a/packages/NetworkStack/AndroidManifest.xml
+++ b/packages/NetworkStack/AndroidManifest.xml
@@ -19,7 +19,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="com.android.networkstack"
   android:sharedUserId="android.uid.networkstack"
-  android:versionCode="200000000"
+  android:versionCode="210000000"
   android:versionName="29 system image"
 >
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
index 530c73a..fb5c16b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java
@@ -19,6 +19,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.PowerManager;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
@@ -33,7 +34,25 @@
 public class BatterySaverUtils {
 
     private static final String TAG = "BatterySaverUtils";
-    public static final String EXTRA_CONFIRM_ONLY = "extra_confirm_only";
+    /**
+     * When set to "true" the notification will be a generic confirm message instead of asking the
+     * user if they want to turn on battery saver. If set to false the dialog will specifically
+     * talk about turning on battery saver and provide a button for taking the action.
+     */
+    public static final String EXTRA_CONFIRM_TEXT_ONLY = "extra_confirm_only";
+    /**
+     * Ignored if {@link #EXTRA_CONFIRM_TEXT_ONLY} is "false". Can be set to any of the values in
+     * {@link PowerManager.AutoPowerSaveModeTriggers}. If set the dialog will set the power
+     * save mode trigger to the specified value after the user acknowledges the trigger.
+     */
+    public static final String EXTRA_POWER_SAVE_MODE_TRIGGER = "extra_power_save_mode_trigger";
+    /**
+     * Ignored if {@link #EXTRA_CONFIRM_TEXT_ONLY} is "false". can be set to any value between
+     * 0-100 that will be used if {@link #EXTRA_POWER_SAVE_MODE_TRIGGER} is
+     * {@link PowerManager#POWER_SAVE_MODE_TRIGGER_PERCENTAGE}.
+     */
+    public static final String EXTRA_POWER_SAVE_MODE_TRIGGER_LEVEL =
+            "extra_power_save_mode_trigger_level";
 
     private BatterySaverUtils() {
     }
@@ -98,7 +117,10 @@
         }
         final ContentResolver cr = context.getContentResolver();
 
-        if (enable && needFirstTimeWarning && maybeShowBatterySaverConfirmation(context, false)) {
+        final Bundle confirmationExtras = new Bundle(1);
+        confirmationExtras.putBoolean(EXTRA_CONFIRM_TEXT_ONLY, false);
+        if (enable && needFirstTimeWarning
+                && maybeShowBatterySaverConfirmation(context, confirmationExtras)) {
             return false;
         }
         if (enable && !needFirstTimeWarning) {
@@ -118,7 +140,7 @@
                         && Global.getInt(cr, Global.LOW_POWER_MODE_TRIGGER_LEVEL, 0) == 0
                         && Secure.getInt(cr,
                         Secure.SUPPRESS_AUTO_BATTERY_SAVER_SUGGESTION, 0) == 0) {
-                    showAutoBatterySaverSuggestion(context, false);
+                    showAutoBatterySaverSuggestion(context, confirmationExtras);
                 }
             }
 
@@ -129,34 +151,36 @@
 
     /**
      * Shows the battery saver confirmation warning if it hasn't been acknowledged by the user in
-     * the past before. When confirmOnly is true, the dialog will have generic info about battery
-     * saver but will only update that the user has been shown the notification and take no
-     * further action. if confirmOnly is false it will show a more specific version of the dialog
-     * that toggles battery saver when acknowledged
+     * the past before. Various extras can be provided that will change the behavior of this
+     * notification as well as the ui for it.
      * @param context A valid context
-     * @param confirmOnly Whether to show the actionless generic dialog (true) or the specific one
-     * that toggles battery saver (false)
+     * @param extras Any extras to include in the intent to trigger this confirmation that will
+     * help the system disambiguate what to show/do
+     *
      * @return True if it showed the notification because it has not been previously acknowledged.
+     * @see #EXTRA_CONFIRM_TEXT_ONLY
+     * @see #EXTRA_POWER_SAVE_MODE_TRIGGER
+     * @see #EXTRA_POWER_SAVE_MODE_TRIGGER_LEVEL
      */
-    public static boolean maybeShowBatterySaverConfirmation(Context context, boolean confirmOnly) {
+    public static boolean maybeShowBatterySaverConfirmation(Context context, Bundle extras) {
         if (Secure.getInt(context.getContentResolver(),
                 Secure.LOW_POWER_WARNING_ACKNOWLEDGED, 0) != 0) {
             return false; // Already shown.
         }
         context.sendBroadcast(
-                getSystemUiBroadcast(ACTION_SHOW_START_SAVER_CONFIRMATION, confirmOnly));
+                getSystemUiBroadcast(ACTION_SHOW_START_SAVER_CONFIRMATION, extras));
         return true;
     }
 
-    private static void showAutoBatterySaverSuggestion(Context context, boolean confirmOnly) {
-        context.sendBroadcast(getSystemUiBroadcast(ACTION_SHOW_AUTO_SAVER_SUGGESTION, confirmOnly));
+    private static void showAutoBatterySaverSuggestion(Context context, Bundle extras) {
+        context.sendBroadcast(getSystemUiBroadcast(ACTION_SHOW_AUTO_SAVER_SUGGESTION, extras));
     }
 
-    private static Intent getSystemUiBroadcast(String action, boolean confirmOnly) {
+    private static Intent getSystemUiBroadcast(String action, Bundle extras) {
         final Intent i = new Intent(action);
         i.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         i.setPackage(SYSUI_PACKAGE);
-        i.putExtra(EXTRA_CONFIRM_ONLY, confirmOnly);
+        i.putExtras(extras);
         return i;
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
index 74057be..ff40d8e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/SettingsInjector.java
@@ -20,14 +20,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.graphics.drawable.Drawable;
 import android.location.SettingInjectorService;
 import android.os.Bundle;
 import android.os.Handler;
@@ -37,9 +35,9 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AttributeSet;
-import android.util.IconDrawableFactory;
 import android.util.Log;
 import android.util.Xml;
 
@@ -56,8 +54,8 @@
 import java.util.ArrayList;
 import java.util.Deque;
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -157,22 +155,8 @@
      * Adds the InjectedSetting information to a Preference object
      */
     private void populatePreference(Preference preference, InjectedSetting setting) {
-        final PackageManager pm = mContext.getPackageManager();
-        Drawable appIcon = null;
-        try {
-            final PackageItemInfo itemInfo = new PackageItemInfo();
-            itemInfo.icon = setting.iconId;
-            itemInfo.packageName = setting.packageName;
-            final ApplicationInfo appInfo = pm.getApplicationInfo(setting.packageName,
-                    PackageManager.GET_META_DATA);
-            appIcon = IconDrawableFactory.newInstance(mContext)
-                    .getBadgedIcon(itemInfo, appInfo, setting.mUserHandle.getIdentifier());
-        } catch (PackageManager.NameNotFoundException e) {
-            Log.e(TAG, "Can't get ApplicationInfo for " + setting.packageName, e);
-        }
         preference.setTitle(setting.title);
         preference.setSummary(R.string.loading_injected_setting_summary);
-        preference.setIcon(appIcon);
         preference.setOnPreferenceClickListener(new ServiceSettingClickedListener(setting));
     }
 
@@ -182,13 +166,15 @@
      * @param profileId Identifier of the user/profile to obtain the injected settings for or
      *                  UserHandle.USER_CURRENT for all profiles associated with current user.
      */
-    public List<Preference> getInjectedSettings(Context prefContext, final int profileId) {
+    public Map<Integer, List<Preference>> getInjectedSettings(Context prefContext,
+            final int profileId) {
         final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         final List<UserHandle> profiles = um.getUserProfiles();
-        ArrayList<Preference> prefs = new ArrayList<>();
+        final ArrayMap<Integer, List<Preference>> result = new ArrayMap<>();
         mSettings.clear();
         for (UserHandle userHandle : profiles) {
             if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) {
+                final List<Preference> prefs = new ArrayList<>();
                 Iterable<InjectedSetting> settings = getSettings(userHandle);
                 for (InjectedSetting setting : settings) {
                     Preference preference = createPreference(prefContext, setting);
@@ -196,12 +182,14 @@
                     prefs.add(preference);
                     mSettings.add(new Setting(setting, preference));
                 }
+                if (!prefs.isEmpty()) {
+                    result.put(userHandle.getIdentifier(), prefs);
+                }
             }
         }
 
         reloadStatusMessages();
-
-        return prefs;
+        return result;
     }
 
     /**
@@ -303,28 +291,6 @@
     }
 
     /**
-     * Checks wheteher there is any preference that other apps have injected.
-     *
-     * @param profileId Identifier of the user/profile to obtain the injected settings for or
-     *                  UserHandle.USER_CURRENT for all profiles associated with current user.
-     */
-    public boolean hasInjectedSettings(final int profileId) {
-        final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        final List<UserHandle> profiles = um.getUserProfiles();
-        final int profileCount = profiles.size();
-        for (int i = 0; i < profileCount; ++i) {
-            final UserHandle userHandle = profiles.get(i);
-            if (profileId == UserHandle.USER_CURRENT || profileId == userHandle.getIdentifier()) {
-                Iterable<InjectedSetting> settings = getSettings(userHandle);
-                for (InjectedSetting setting : settings) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
      * Reloads the status messages for all the preference items.
      */
     public void reloadStatusMessages() {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
index 42600c1..834f4fc 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QSTile.java
@@ -117,6 +117,7 @@
         public String expandedAccessibilityClassName;
         public SlashState slash;
         public boolean handlesLongClick = true;
+        public boolean showRippleEffect = true;
 
         public boolean copyTo(State other) {
             if (other == null) throw new IllegalArgumentException();
@@ -135,7 +136,8 @@
                     || !Objects.equals(other.isTransient, isTransient)
                     || !Objects.equals(other.dualTarget, dualTarget)
                     || !Objects.equals(other.slash, slash)
-                    || !Objects.equals(other.handlesLongClick, handlesLongClick);
+                    || !Objects.equals(other.handlesLongClick, handlesLongClick)
+                    || !Objects.equals(other.showRippleEffect, showRippleEffect);
             other.icon = icon;
             other.iconSupplier = iconSupplier;
             other.label = label;
@@ -149,6 +151,7 @@
             other.isTransient = isTransient;
             other.slash = slash != null ? slash.copy() : null;
             other.handlesLongClick = handlesLongClick;
+            other.showRippleEffect = showRippleEffect;
             return changed;
         }
 
diff --git a/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml b/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml
new file mode 100644
index 0000000..fe1bb26
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml
@@ -0,0 +1,31 @@
+<!--
+     Copyright (C) 2019 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:viewportWidth="22"
+    android:viewportHeight="17"
+    android:width="22dp"
+    android:height="17dp">
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M1.22,8.49l0.43-4.96h4.33v1.17H2.67L2.44,7.41c0.41-0.29,0.85-0.43,1.33-0.43c0.77,0,1.38,0.3,1.83,0.9 s0.66,1.41,0.66,2.43c0,1.03-0.24,1.84-0.72,2.43s-1.14,0.88-1.98,0.88c-0.75,0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07,0.57,0.23,1,0.49,1.29c0.26,0.29,0.59,0.43,1.01,0.43c0.47,0,0.84-0.2,1.1-0.61c0.26-0.41,0.4-0.96,0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.96,8.11,3.47,8.11c-0.4,0-0.72,0.1-0.96,0.31L2.19,8.75L1.22,8.49z" />
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M14.14,12.24l-0.22,0.27c-0.63,0.73-1.55,1.1-2.76,1.1c-1.08,0-1.92-0.36-2.53-1.07c-0.61-0.71-0.93-1.72-0.94-3.02V7.56 c0-1.39,0.28-2.44,0.84-3.13c0.56-0.7,1.39-1.04,2.51-1.04c0.95,0,1.69,0.26,2.23,0.79c0.54,0.53,0.83,1.28,0.89,2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45c-0.29-0.35-0.74-0.52-1.34-0.52c-0.72,0-1.24,0.23-1.57,0.7C9.14,5.63,8.96,6.37,8.95,7.4v2.03 c0,1,0.19,1.77,0.57,2.31c0.38,0.54,0.93,0.8,1.65,0.8c0.67,0,1.19-0.16,1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z" />
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M20.96,8.88h-3.52v3.53h4.1v1.07h-5.35V3.52h5.28V4.6h-4.03V7.8h3.52V8.88z" />
+
+</vector>
diff --git a/packages/SystemUI/res/values-mcc310-mnc030/config.xml b/packages/SystemUI/res/values-mcc310-mnc030/config.xml
new file mode 100644
index 0000000..26b9192
--- /dev/null
+++ b/packages/SystemUI/res/values-mcc310-mnc030/config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2019, 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Enable 5 bar signal strength icon -->
+    <bool name="config_inflateSignalStrength">true</bool>
+</resources>
+
diff --git a/packages/SystemUI/res/values-mcc310-mnc070/config.xml b/packages/SystemUI/res/values-mcc310-mnc070/config.xml
new file mode 100644
index 0000000..26b9192
--- /dev/null
+++ b/packages/SystemUI/res/values-mcc310-mnc070/config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2019, 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Enable 5 bar signal strength icon -->
+    <bool name="config_inflateSignalStrength">true</bool>
+</resources>
+
diff --git a/packages/SystemUI/res/values-mcc310-mnc170/config.xml b/packages/SystemUI/res/values-mcc310-mnc170/config.xml
new file mode 100644
index 0000000..26b9192
--- /dev/null
+++ b/packages/SystemUI/res/values-mcc310-mnc170/config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2019, 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Enable 5 bar signal strength icon -->
+    <bool name="config_inflateSignalStrength">true</bool>
+</resources>
+
diff --git a/packages/SystemUI/res/values-mcc310-mnc280/config.xml b/packages/SystemUI/res/values-mcc310-mnc280/config.xml
new file mode 100644
index 0000000..26b9192
--- /dev/null
+++ b/packages/SystemUI/res/values-mcc310-mnc280/config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2019, 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Enable 5 bar signal strength icon -->
+    <bool name="config_inflateSignalStrength">true</bool>
+</resources>
+
diff --git a/packages/SystemUI/res/values-mcc310-mnc380/config.xml b/packages/SystemUI/res/values-mcc310-mnc380/config.xml
new file mode 100644
index 0000000..26b9192
--- /dev/null
+++ b/packages/SystemUI/res/values-mcc310-mnc380/config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2019, 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Enable 5 bar signal strength icon -->
+    <bool name="config_inflateSignalStrength">true</bool>
+</resources>
+
diff --git a/packages/SystemUI/res/values-mcc310-mnc410/config.xml b/packages/SystemUI/res/values-mcc310-mnc410/config.xml
new file mode 100644
index 0000000..26b9192
--- /dev/null
+++ b/packages/SystemUI/res/values-mcc310-mnc410/config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2019, 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Enable 5 bar signal strength icon -->
+    <bool name="config_inflateSignalStrength">true</bool>
+</resources>
+
diff --git a/packages/SystemUI/res/values-mcc310-mnc560/config.xml b/packages/SystemUI/res/values-mcc310-mnc560/config.xml
new file mode 100644
index 0000000..26b9192
--- /dev/null
+++ b/packages/SystemUI/res/values-mcc310-mnc560/config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2019, 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Enable 5 bar signal strength icon -->
+    <bool name="config_inflateSignalStrength">true</bool>
+</resources>
+
diff --git a/packages/SystemUI/res/values-mcc310-mnc950/config.xml b/packages/SystemUI/res/values-mcc310-mnc950/config.xml
new file mode 100644
index 0000000..26b9192
--- /dev/null
+++ b/packages/SystemUI/res/values-mcc310-mnc950/config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2019, 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Enable 5 bar signal strength icon -->
+    <bool name="config_inflateSignalStrength">true</bool>
+</resources>
+
diff --git a/packages/SystemUI/res/values-mcc311-mnc180/config.xml b/packages/SystemUI/res/values-mcc311-mnc180/config.xml
new file mode 100644
index 0000000..26b9192
--- /dev/null
+++ b/packages/SystemUI/res/values-mcc311-mnc180/config.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2019, 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.
+*/
+-->
+
+<!-- These resources are around just to allow their values to be customized
+     for different hardware and product builds. -->
+<resources>
+    <!-- Enable 5 bar signal strength icon -->
+    <bool name="config_inflateSignalStrength">true</bool>
+</resources>
+
diff --git a/packages/SystemUI/res/values/internal.xml b/packages/SystemUI/res/values/internal.xml
index 930cfce..c29a51f 100644
--- a/packages/SystemUI/res/values/internal.xml
+++ b/packages/SystemUI/res/values/internal.xml
@@ -17,6 +17,7 @@
 <resources>
     <dimen name="status_bar_height">@*android:dimen/status_bar_height</dimen>
     <dimen name="navigation_bar_height">@*android:dimen/navigation_bar_height</dimen>
+    <dimen name="navigation_bar_frame_height">@*android:dimen/navigation_bar_frame_height</dimen>
     <dimen name="navigation_bar_height_car_mode">@*android:dimen/navigation_bar_height_car_mode</dimen>
 </resources>
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f47d4b5..e098bc5 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -424,6 +424,9 @@
     <!-- Content description of the data connection type LTE+. [CHAR LIMIT=NONE] -->
     <string name="data_connection_lte_plus">LTE+</string>
 
+    <!-- Content description of the data connection type 5Ge. [CHAR LIMIT=NONE] -->
+    <string name="data_connection_5ge" translate="false">5Ge</string>
+
     <!-- Content description of the data connection type 5G. [CHAR LIMIT=NONE] -->
     <string name="data_connection_5g" translate="false">5G</string>
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index b738b57..70366a8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -138,6 +138,7 @@
             ClockManager clockManager) {
         super(context, attrs);
         mStatusBarStateController = statusBarStateController;
+        mStatusBarState = mStatusBarStateController.getState();
         mSysuiColorExtractor = colorExtractor;
         mClockManager = clockManager;
         mTransition = new ClockBoundsTransition();
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 64e56f9..bc00b5c 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -20,8 +20,10 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.ContentObserver;
+import android.net.Uri;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.ArrayMap;
 import android.util.DisplayMetrics;
@@ -35,6 +37,7 @@
 import com.android.systemui.dock.DockManager.DockEventListener;
 import com.android.systemui.plugins.ClockPlugin;
 import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.util.InjectionInflationController;
 
@@ -61,6 +64,7 @@
     private final ContentResolver mContentResolver;
     private final SettingsWrapper mSettingsWrapper;
     private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+    private final CurrentUserTracker mCurrentUserTracker;
 
     /**
      * Observe settings changes to know when to switch the clock face.
@@ -68,9 +72,11 @@
     private final ContentObserver mContentObserver =
             new ContentObserver(mMainHandler) {
                 @Override
-                public void onChange(boolean selfChange) {
-                    super.onChange(selfChange);
-                    reload();
+                public void onChange(boolean selfChange, Uri uri, int userId) {
+                    super.onChange(selfChange, uri, userId);
+                    if (userId == mCurrentUserTracker.getCurrentUserId()) {
+                        reload();
+                    }
                 }
             };
 
@@ -123,6 +129,12 @@
         mPluginManager = pluginManager;
         mContentResolver = contentResolver;
         mSettingsWrapper = settingsWrapper;
+        mCurrentUserTracker = new CurrentUserTracker(context) {
+            @Override
+            public void onUserSwitched(int newUserId) {
+                reload();
+            }
+        };
         mPreviewClocks = new AvailableClocks();
 
         Resources res = context.getResources();
@@ -203,10 +215,11 @@
         mPluginManager.addPluginListener(mPreviewClocks, ClockPlugin.class, true);
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
-                false, mContentObserver);
+                false, mContentObserver, UserHandle.USER_ALL);
         mContentResolver.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.DOCKED_CLOCK_FACE),
-                false, mContentObserver);
+                false, mContentObserver, UserHandle.USER_ALL);
+        mCurrentUserTracker.startTracking();
         if (mDockManager == null) {
             mDockManager = SysUiServiceProvider.getComponent(mContext, DockManager.class);
         }
@@ -218,6 +231,7 @@
     private void unregister() {
         mPluginManager.removePluginListener(mPreviewClocks);
         mContentResolver.unregisterContentObserver(mContentObserver);
+        mCurrentUserTracker.stopTracking();
         if (mDockManager != null) {
             mDockManager.removeListener(mDockEventListener);
         }
@@ -334,7 +348,8 @@
         private ClockPlugin getClockPlugin() {
             ClockPlugin plugin = null;
             if (ClockManager.this.isDocked()) {
-                final String name = mSettingsWrapper.getDockedClockFace();
+                final String name = mSettingsWrapper.getDockedClockFace(
+                        mCurrentUserTracker.getCurrentUserId());
                 if (name != null) {
                     plugin = mClocks.get(name);
                     if (plugin != null) {
@@ -342,7 +357,8 @@
                     }
                 }
             }
-            final String name = mSettingsWrapper.getLockScreenCustomClockFace();
+            final String name = mSettingsWrapper.getLockScreenCustomClockFace(
+                    mCurrentUserTracker.getCurrentUserId());
             if (name != null) {
                 plugin = mClocks.get(name);
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java b/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java
index 58e1155..e1c658be 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/SettingsWrapper.java
@@ -34,15 +34,19 @@
 
     /**
      * Gets the value stored in settings for the custom clock face.
+     *
+     * @param userId ID of the user.
      */
-    public String getLockScreenCustomClockFace() {
-        return Settings.Secure.getString(mContentResolver, CUSTOM_CLOCK_FACE);
+    public String getLockScreenCustomClockFace(int userId) {
+        return Settings.Secure.getStringForUser(mContentResolver, CUSTOM_CLOCK_FACE, userId);
     }
 
     /**
      * Gets the value stored in settings for the clock face to use when docked.
+     *
+     * @param userId ID of the user.
      */
-    public String getDockedClockFace() {
-        return Settings.Secure.getString(mContentResolver, DOCKED_CLOCK_FACE);
+    public String getDockedClockFace(int userId) {
+        return Settings.Secure.getStringForUser(mContentResolver, DOCKED_CLOCK_FACE, userId);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 4fe09a9..665df77 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -26,16 +26,41 @@
  */
 class Bubble {
 
+    private final String mKey;
+    private final BubbleExpandedView.OnBubbleBlockedListener mListener;
+
+    private boolean mInflated;
+
     public BubbleView iconView;
     public BubbleExpandedView expandedView;
-    public String key;
     public NotificationEntry entry;
 
+    Bubble(NotificationEntry e, BubbleExpandedView.OnBubbleBlockedListener listener) {
+        entry = e;
+        mKey = e.key;
+        mListener = listener;
+    }
+
+    /** @deprecated use the other constructor to defer View creation. */
+    @Deprecated
     Bubble(NotificationEntry e, LayoutInflater inflater, BubbleStackView stackView,
             BubbleExpandedView.OnBubbleBlockedListener listener) {
-        entry = e;
-        key = entry.key;
+        this(e, listener);
+        inflate(inflater, stackView);
+    }
 
+    public String getKey() {
+        return mKey;
+    }
+
+    boolean isInflated() {
+        return mInflated;
+    }
+
+    void inflate(LayoutInflater inflater, BubbleStackView stackView) {
+        if (mInflated) {
+            return;
+        }
         iconView = (BubbleView) inflater.inflate(
                 R.layout.bubble_view, stackView, false /* attachToRoot */);
         iconView.setNotif(entry);
@@ -44,12 +69,14 @@
                 R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
         expandedView.setEntry(entry, stackView);
 
-        expandedView.setOnBlockedListener(listener);
+        expandedView.setOnBlockedListener(mListener);
+        mInflated = true;
     }
 
-    public void setEntry(NotificationEntry entry) {
-        key = entry.key;
-        iconView.update(entry);
-        expandedView.update(entry);
+    void setEntry(NotificationEntry entry) {
+        if (mInflated) {
+            iconView.update(entry);
+            expandedView.update(entry);
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 5acf3c2..418d052 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -220,6 +220,26 @@
         mSurfaceSynchronizer = synchronizer;
     }
 
+    /**
+     * BubbleStackView is lazily created by this method the first time a Bubble is added. This
+     * method initializes the stack view and adds it to the StatusBar just above the scrim.
+     */
+    private void ensureStackViewCreated() {
+        if (mStackView == null) {
+            mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
+            ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
+            // TODO(b/130237686): When you expand the shade on top of expanded bubble, there is no
+            //  scrim between bubble and the shade
+            int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
+            sbv.addView(mStackView, bubblePosition,
+                    new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+            if (mExpandListener != null) {
+                mStackView.setExpandListener(mExpandListener);
+            }
+            mStackView.setOnBlockedListener(this);
+        }
+    }
+
     @Override
     public void onUiModeChanged() {
         if (mStackView != null) {
@@ -325,27 +345,15 @@
     /**
      * Adds or updates a bubble associated with the provided notification entry.
      *
-     * @param notif          the notification associated with this bubble.
+     * @param notif the notification associated with this bubble.
      */
     void updateBubble(NotificationEntry notif) {
         if (mStackView != null && mBubbleData.getBubble(notif.key) != null) {
             // It's an update
             mStackView.updateBubble(notif);
         } else {
-            if (mStackView == null) {
-                mStackView = new BubbleStackView(mContext, mBubbleData, mSurfaceSynchronizer);
-                ViewGroup sbv = mStatusBarWindowController.getStatusBarView();
-                // XXX: Bug when you expand the shade on top of expanded bubble, there is no scrim
-                // between bubble and the shade
-                int bubblePosition = sbv.indexOfChild(sbv.findViewById(R.id.scrim_behind)) + 1;
-                sbv.addView(mStackView, bubblePosition,
-                        new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
-                if (mExpandListener != null) {
-                    mStackView.setExpandListener(mExpandListener);
-                }
-                mStackView.setOnBlockedListener(this);
-            }
             // It's new
+            ensureStackViewCreated();
             mStackView.addBubble(notif);
         }
         if (shouldAutoExpand(notif)) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index cf70287..fe3f9d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -106,7 +106,7 @@
     }
 
     public void addBubble(Bubble b) {
-        mBubbles.put(b.key, b);
+        mBubbles.put(b.getKey(), b);
     }
 
     @Nullable
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
index d1939d0..477e7d7e 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/ImageProcessHelper.java
@@ -85,9 +85,7 @@
             Bitmap bitmap = bitmaps[0];
             if (bitmap != null) {
                 int[] histogram = processHistogram(bitmap);
-                Float val = computePercentile85(bitmap, histogram);
-                bitmaps[0] = null;
-                return val;
+                return computePercentile85(bitmap, histogram);
             }
             Log.e(TAG, "Per85ComputeTask: Can't get bitmap");
             return DEFAULT_PER85;
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 10f727b..e92aa51 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -22,15 +22,19 @@
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioAttributes;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
 import android.text.Annotation;
 import android.text.Layout;
@@ -547,9 +551,15 @@
         updateNotification();
     }
 
-    private void showStartSaverConfirmation(boolean confirmOnly) {
+    private void showStartSaverConfirmation(Bundle extras) {
         if (mSaverConfirmation != null) return;
         final SystemUIDialog d = new SystemUIDialog(mContext);
+        final boolean confirmOnly = extras.getBoolean(BatterySaverUtils.EXTRA_CONFIRM_TEXT_ONLY);
+        final int batterySaverTriggerMode =
+                extras.getInt(BatterySaverUtils.EXTRA_POWER_SAVE_MODE_TRIGGER,
+                        PowerManager.POWER_SAVE_MODE_TRIGGER_PERCENTAGE);
+        final int batterySaverTriggerLevel =
+                extras.getInt(BatterySaverUtils.EXTRA_POWER_SAVE_MODE_TRIGGER_LEVEL, 0);
         d.setMessage(getBatterySaverDescription());
 
         // Sad hack for http://b/78261259 and http://b/78298335. Otherwise "Battery" may be split
@@ -563,14 +573,25 @@
         if (confirmOnly) {
             d.setTitle(R.string.battery_saver_confirmation_title_generic);
             d.setPositiveButton(com.android.internal.R.string.confirm_battery_saver,
-                    (dialog, which) -> Secure.putInt(
-                            mContext.getContentResolver(),
-                            Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
-                            1));
+                    (dialog, which) -> {
+                        final ContentResolver resolver = mContext.getContentResolver();
+                        Secure.putInt(
+                                resolver,
+                                Secure.LOW_POWER_WARNING_ACKNOWLEDGED,
+                                1);
+                        Settings.Global.putInt(
+                                resolver,
+                                Global.AUTOMATIC_POWER_SAVE_MODE,
+                                batterySaverTriggerMode);
+                        Settings.Global.putInt(
+                                resolver,
+                                Global.LOW_POWER_MODE_TRIGGER_LEVEL,
+                                batterySaverTriggerLevel);
+                    });
         } else {
             d.setTitle(R.string.battery_saver_confirmation_title);
             d.setPositiveButton(R.string.battery_saver_confirmation_ok,
-                (dialog, which) -> setSaverMode(true, false));
+                    (dialog, which) -> setSaverMode(true, false));
             d.setNegativeButton(android.R.string.cancel, null);
         }
         d.setShowForAllUsers(true);
@@ -731,7 +752,7 @@
                 dismissLowBatteryNotification();
             } else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) {
                 dismissLowBatteryNotification();
-                showStartSaverConfirmation(intent.getBooleanExtra(EXTRA_CONFIRM_ONLY, false));
+                showStartSaverConfirmation(intent.getExtras());
             } else if (action.equals(ACTION_DISMISSED_WARNING)) {
                 dismissLowBatteryWarning();
             } else if (ACTION_CLICKED_TEMP_WARNING.equals(action)) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
index d40973b..a732a25 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileBaseView.java
@@ -40,7 +40,6 @@
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
-import android.widget.ImageView.ScaleType;
 import android.widget.Switch;
 
 import com.android.settingslib.Utils;
@@ -63,6 +62,7 @@
     private boolean mTileState;
     private boolean mCollapsedView;
     private boolean mClicked;
+    private boolean mShowRippleEffect = true;
 
     private final ImageView mBg;
     private final int mColorActive;
@@ -209,6 +209,7 @@
             mCircleColor = circleColor;
         }
 
+        mShowRippleEffect = state.showRippleEffect;
         setClickable(state.state != Tile.STATE_UNAVAILABLE);
         setLongClickable(state.handlesLongClick);
         mIcon.setIcon(state, allowAnimations);
@@ -254,7 +255,7 @@
     @Override
     public void setClickable(boolean clickable) {
         super.setClickable(clickable);
-        setBackground(clickable ? mRipple : null);
+        setBackground(clickable && mShowRippleEffect ? mRipple : null);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index c664a20..d62f10d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -16,6 +16,7 @@
 package com.android.systemui.qs.tiles;
 
 import android.content.Intent;
+import android.provider.Settings.Secure;
 import android.service.quicksettings.Tile;
 import android.widget.Switch;
 
@@ -23,6 +24,7 @@
 import com.android.systemui.R;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
 import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.SecureSetting;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.BatteryController;
 
@@ -32,6 +34,7 @@
         BatteryController.BatteryStateChangeCallback {
 
     private final BatteryController mBatteryController;
+    private final SecureSetting mSetting;
 
     private int mLevel;
     private boolean mPowerSave;
@@ -45,6 +48,12 @@
         super(host);
         mBatteryController = batteryController;
         mBatteryController.observe(getLifecycle(), this);
+        mSetting = new SecureSetting(mContext, mHandler, Secure.LOW_POWER_WARNING_ACKNOWLEDGED) {
+            @Override
+            protected void handleValueChanged(int value, boolean observedChange) {
+                handleRefreshState(null);
+            }
+        };
     }
 
     @Override
@@ -53,12 +62,19 @@
     }
 
     @Override
+    protected void handleDestroy() {
+        super.handleDestroy();
+        mSetting.setListening(false);
+    }
+
+    @Override
     public int getMetricsCategory() {
         return MetricsEvent.QS_BATTERY_TILE;
     }
 
     @Override
     public void handleSetListening(boolean listening) {
+        mSetting.setListening(listening);
     }
 
     @Override
@@ -88,6 +104,7 @@
         state.contentDescription = state.label;
         state.value = mPowerSave;
         state.expandedAccessibilityClassName = Switch.class.getName();
+        state.showRippleEffect = mSetting.getValue() == 0;
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 4d6693f..7b6fb94 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -631,7 +631,9 @@
 
     public void notifyAssistantVisibilityChanged(float visibility) {
         try {
-            mOverviewProxy.onAssistantVisibilityChanged(visibility);
+            if (mOverviewProxy != null) {
+                mOverviewProxy.onAssistantVisibilityChanged(visibility);
+            }
         } catch (RemoteException e) {
             Log.e(TAG_OPS, "Failed to call onAssistantVisibilityChanged()", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index a87e50c..3441591 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -83,7 +83,7 @@
 
     private final int mSlowThreshold;
     private final int mFastThreshold;
-    private LockIcon mLockIcon;
+    private final LockIcon mLockIcon;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
 
     private String mRestingIndication;
@@ -539,7 +539,6 @@
 
     protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
         public static final int HIDE_DELAY_MS = 5000;
-        private int mLastSuccessiveErrorMessage = -1;
 
         @Override
         public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
@@ -577,20 +576,14 @@
             if (!updateMonitor.isUnlockingWithBiometricAllowed()) {
                 return;
             }
+            animatePadlockError();
             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
                 mStatusBarKeyguardViewManager.showBouncerMessage(helpString,
                         mInitialTextColorState);
             } else if (updateMonitor.isScreenOn()) {
-                mLockIcon.setTransientBiometricsError(true);
                 showTransientIndication(helpString);
                 hideTransientIndicationDelayed(TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
-                mHandler.removeMessages(MSG_CLEAR_BIOMETRIC_MSG);
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_BIOMETRIC_MSG),
-                        TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
             }
-            // Help messages indicate that there was actually a try since the last error, so those
-            // are not two successive error messages anymore.
-            mLastSuccessiveErrorMessage = -1;
         }
 
         @Override
@@ -600,15 +593,9 @@
             if (shouldSuppressBiometricError(msgId, biometricSourceType, updateMonitor)) {
                 return;
             }
+            animatePadlockError();
             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
-                // When swiping up right after receiving a biometric error, the bouncer calls
-                // authenticate leading to the same message being shown again on the bouncer.
-                // We want to avoid this, as it may confuse the user when the message is too
-                // generic.
-                if (mLastSuccessiveErrorMessage != msgId) {
-                    mStatusBarKeyguardViewManager.showBouncerMessage(errString,
-                            mInitialTextColorState);
-                }
+                mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState);
             } else if (updateMonitor.isScreenOn()) {
                 showTransientIndication(errString);
                 // We want to keep this message around in case the screen was off
@@ -616,7 +603,13 @@
             } else {
                 mMessageToShowOnScreenOn = errString;
             }
-            mLastSuccessiveErrorMessage = msgId;
+        }
+
+        private void animatePadlockError() {
+            mLockIcon.setTransientBiometricsError(true);
+            mHandler.removeMessages(MSG_CLEAR_BIOMETRIC_MSG);
+            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_BIOMETRIC_MSG),
+                    TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
         }
 
         private boolean shouldSuppressBiometricError(int msgId,
@@ -670,21 +663,19 @@
         @Override
         public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
             super.onBiometricAuthenticated(userId, biometricSourceType);
-            mLastSuccessiveErrorMessage = -1;
             mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT);
         }
 
         @Override
-        public void onBiometricAuthFailed(BiometricSourceType biometricSourceType) {
-            super.onBiometricAuthFailed(biometricSourceType);
-            mLastSuccessiveErrorMessage = -1;
-        }
-
-        @Override
         public void onUserUnlocked() {
             if (mVisible) {
                 updateIndication(false);
             }
         }
+
+        @Override
+        public void onKeyguardBouncerChanged(boolean bouncer) {
+            mLockIcon.setBouncerVisible(bouncer);
+        }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 3a6756b..79bf6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -17,6 +17,7 @@
 
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
+import android.content.res.Resources;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -140,6 +141,7 @@
     private WindowManager.LayoutParams mEdgePanelLp;
 
     public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService) {
+        final Resources res = context.getResources();
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = context.getMainExecutor();
@@ -148,10 +150,9 @@
 
         mEdgeWidth = QuickStepContract.getEdgeSensitivityWidth(context);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
-        mSwipeThreshold = context.getResources()
-                .getDimension(R.dimen.navigation_edge_action_drag_threshold);
+        mSwipeThreshold = res.getDimension(R.dimen.navigation_edge_action_drag_threshold);
 
-        mNavBarHeight = context.getResources().getDimensionPixelSize(R.dimen.navigation_bar_height);
+        mNavBarHeight = res.getDimensionPixelSize(R.dimen.navigation_bar_frame_height);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
index 6ebd6b3..3cc4a7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockIcon.java
@@ -57,8 +57,10 @@
     private int mDensity;
     private boolean mPulsing;
     private boolean mDozing;
+    private boolean mBouncerVisible;
     private boolean mLastDozing;
     private boolean mLastPulsing;
+    private boolean mLastBouncerVisible;
 
     private final Runnable mDrawOffTimeout = () -> update(true /* forceUpdate */);
     private float mDarkAmount;
@@ -109,9 +111,9 @@
         int state = getState();
         mIsFaceUnlockState = state == STATE_SCANNING_FACE;
         if (state != mLastState || mLastDozing != mDozing || mLastPulsing != mPulsing
-                || mLastScreenOn != mScreenOn || force) {
+                || mLastScreenOn != mScreenOn || mLastBouncerVisible != mBouncerVisible || force) {
             int iconAnimRes = getAnimationResForTransition(mLastState, state, mLastPulsing,
-                    mPulsing, mLastDozing, mDozing);
+                    mPulsing, mLastDozing, mDozing, mBouncerVisible);
             boolean isAnim = iconAnimRes != -1;
 
             Drawable icon;
@@ -159,6 +161,7 @@
             mLastScreenOn = mScreenOn;
             mLastDozing = mDozing;
             mLastPulsing = mPulsing;
+            mLastBouncerVisible = mBouncerVisible;
         }
 
         setVisibility(mDozing && !mPulsing ? GONE : VISIBLE);
@@ -231,8 +234,8 @@
     }
 
     private static int getAnimationResForTransition(int oldState, int newState,
-            boolean wasPulsing, boolean pulsing,
-            boolean wasDozing, boolean dozing) {
+            boolean wasPulsing, boolean pulsing, boolean wasDozing, boolean dozing,
+            boolean bouncerVisible) {
 
         // Never animate when screen is off
         if (dozing && !pulsing) {
@@ -249,7 +252,7 @@
             return com.android.internal.R.anim.lock_unlock;
         } else if (justLocked) {
             return com.android.internal.R.anim.lock_lock;
-        } else if (newState == STATE_SCANNING_FACE) {
+        } else if (newState == STATE_SCANNING_FACE && bouncerVisible) {
             return com.android.internal.R.anim.lock_scanning;
         } else if (!wasPulsing && pulsing && newState != STATE_LOCK_OPEN) {
             return com.android.internal.R.anim.lock_in;
@@ -298,4 +301,15 @@
         int color = ColorUtils.blendARGB(Color.TRANSPARENT, Color.WHITE, mDarkAmount);
         drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
     }
+
+    /**
+     * If bouncer is visible or not.
+     */
+    public void setBouncerVisible(boolean bouncerVisible) {
+        if (mBouncerVisible == bouncerVisible) {
+            return;
+        }
+        mBouncerVisible = bouncerVisible;
+        update();
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index c5996a1..8f135c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -47,6 +47,8 @@
 import java.io.PrintWriter;
 import java.util.BitSet;
 import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 
 public class MobileSignalController extends SignalController<
@@ -73,6 +75,9 @@
     private SignalStrength mSignalStrength;
     private MobileIconGroup mDefaultIcons;
     private Config mConfig;
+    private boolean mInflateSignalStrengths = false;
+    // Some specific carriers have 5GE network which is special LTE CA network.
+    private static final int NETWORK_TYPE_LTE_CA_5GE = TelephonyManager.MAX_NETWORK_TYPE + 1;
 
     // TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
     // need listener lists anymore.
@@ -114,6 +119,7 @@
 
     public void setConfiguration(Config config) {
         mConfig = config;
+        updateInflateSignalStrength();
         mapIconSets();
         updateTelephony();
     }
@@ -236,11 +242,19 @@
                         TelephonyIcons.LTE_PLUS);
             }
         }
+        mNetworkToIconLookup.put(NETWORK_TYPE_LTE_CA_5GE,
+                TelephonyIcons.LTE_CA_5G_E);
         mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyIcons.WFC);
     }
 
+    private void updateInflateSignalStrength() {
+        mInflateSignalStrengths = SubscriptionManager.getResourcesForSubId(mContext,
+               mSubscriptionInfo.getSubscriptionId())
+               .getBoolean(R.bool.config_inflateSignalStrength);
+    }
+
     private int getNumLevels() {
-        if (mConfig.inflateSignalStrengths) {
+        if (mInflateSignalStrengths) {
             return SignalStrength.NUM_SIGNAL_STRENGTH_BINS + 1;
         }
         return SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
@@ -252,7 +266,7 @@
             return SignalDrawable.getCarrierChangeState(getNumLevels());
         } else if (mCurrentState.connected) {
             int level = mCurrentState.level;
-            if (mConfig.inflateSignalStrengths) {
+            if (mInflateSignalStrengths) {
                 level++;
             }
             boolean dataDisabled = mCurrentState.userSetup
@@ -381,6 +395,26 @@
         }
     }
 
+    private boolean isCarrierSpecificDataIcon() {
+        if (mConfig.patternOfCarrierSpecificDataIcon == null
+                || mConfig.patternOfCarrierSpecificDataIcon.length() == 0) {
+            return false;
+        }
+
+        Pattern stringPattern = Pattern.compile(mConfig.patternOfCarrierSpecificDataIcon);
+        String[] operatorNames = new String[]{mServiceState.getOperatorAlphaLongRaw(),
+                mServiceState.getOperatorAlphaShortRaw()};
+        for (String opName : operatorNames) {
+            if (!TextUtils.isEmpty(opName)) {
+                Matcher matcher = stringPattern.matcher(opName);
+                if (matcher.find()) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Updates the network's name based on incoming spn and plmn.
      */
@@ -535,6 +569,7 @@
         pw.println("  mSignalStrength=" + mSignalStrength + ",");
         pw.println("  mDataState=" + mDataState + ",");
         pw.println("  mDataNetType=" + mDataNetType + ",");
+        pw.println("  mInflateSignalStrengths=" + mInflateSignalStrengths + ",");
     }
 
     class MobilePhoneStateListener extends PhoneStateListener {
@@ -559,12 +594,8 @@
                         + " dataState=" + state.getDataRegState());
             }
             mServiceState = state;
-            if (state != null) {
-                mDataNetType = state.getDataNetworkType();
-                if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE && mServiceState != null &&
-                        mServiceState.isUsingCarrierAggregation()) {
-                    mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA;
-                }
+            if (mServiceState != null) {
+                updateDataNetType(mServiceState.getDataNetworkType());
             }
             updateTelephony();
         }
@@ -576,14 +607,21 @@
                         + " type=" + networkType);
             }
             mDataState = state;
-            mDataNetType = networkType;
-            if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE && mServiceState != null &&
-                    mServiceState.isUsingCarrierAggregation()) {
-                mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA;
-            }
+            updateDataNetType(networkType);
             updateTelephony();
         }
 
+        private void updateDataNetType(int networkType) {
+            mDataNetType = networkType;
+            if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE) {
+                if (isCarrierSpecificDataIcon()) {
+                    mDataNetType = NETWORK_TYPE_LTE_CA_5GE;
+                } else if (mServiceState != null && mServiceState.isUsingCarrierAggregation()) {
+                    mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA;
+                }
+            }
+        }
+
         @Override
         public void onDataActivity(int direction) {
             if (DEBUG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index d01430a..faf63c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -55,6 +55,7 @@
 import android.util.MathUtils;
 import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
@@ -108,16 +109,10 @@
     private final SubscriptionDefaults mSubDefaults;
     private final DataSaverController mDataSaverController;
     private final CurrentUserTracker mUserTracker;
+    private final Object mLock = new Object();
     private Config mConfig;
 
-    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
-        @Override
-        public void onActiveDataSubscriptionIdChanged(int subId) {
-            mActiveMobileDataSubscription = subId;
-            doUpdateMobileControllers();
-        }
-    };
-
+    private PhoneStateListener mPhoneStateListener;
     private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
 
     // Subcontrollers.
@@ -279,6 +274,14 @@
         // TODO: Move off of the deprecated CONNECTIVITY_ACTION broadcast and rely on callbacks
         // exclusively for status bar icons.
         mConnectivityManager.registerDefaultNetworkCallback(callback, mReceiverHandler);
+        // Register the listener on our bg looper
+        mPhoneStateListener = new PhoneStateListener(bgLooper) {
+            @Override
+            public void onActiveDataSubscriptionIdChanged(int subId) {
+                mActiveMobileDataSubscription = subId;
+                doUpdateMobileControllers();
+            }
+        };
     }
 
     public DataSaverController getDataSaverController() {
@@ -600,7 +603,9 @@
             updateNoSims();
             return;
         }
-        setCurrentSubscriptions(subscriptions);
+        synchronized (mLock) {
+            setCurrentSubscriptionsLocked(subscriptions);
+        }
         updateNoSims();
         recalculateEmergency();
     }
@@ -628,8 +633,9 @@
         return false;
     }
 
+    @GuardedBy("mLock")
     @VisibleForTesting
-    void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) {
+    public void setCurrentSubscriptionsLocked(List<SubscriptionInfo> subscriptions) {
         Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
             @Override
             public int compare(SubscriptionInfo lhs, SubscriptionInfo rhs) {
@@ -1102,6 +1108,7 @@
         boolean hspaDataDistinguishable;
         boolean inflateSignalStrengths = false;
         boolean alwaysShowDataRatIcon = false;
+        public String patternOfCarrierSpecificDataIcon = "";
 
         /**
          * Mapping from NR 5G status string to an integer. The NR 5G status string should match
@@ -1140,6 +1147,8 @@
                         CarrierConfigManager.KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL);
                 config.hideLtePlus = b.getBoolean(
                         CarrierConfigManager.KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL);
+                config.patternOfCarrierSpecificDataIcon = b.getString(
+                        CarrierConfigManager.KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING);
                 String nr5GIconConfiguration =
                         b.getString(CarrierConfigManager.KEY_5G_ICON_CONFIGURATION_STRING);
                 if (!TextUtils.isEmpty(nr5GIconConfiguration)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index e151ca3..c22ff8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -35,6 +35,7 @@
     static final int ICON_3G = R.drawable.ic_3g_mobiledata;
     static final int ICON_4G = R.drawable.ic_4g_mobiledata;
     static final int ICON_4G_PLUS = R.drawable.ic_4g_plus_mobiledata;
+    static final int ICON_5G_E = R.drawable.ic_5g_e_mobiledata;
     static final int ICON_1X = R.drawable.ic_1x_mobiledata;
     static final int ICON_5G = R.drawable.ic_5g_mobiledata;
     static final int ICON_5G_PLUS = R.drawable.ic_5g_plus_mobiledata;
@@ -204,6 +205,19 @@
             TelephonyIcons.ICON_LTE_PLUS,
             true);
 
+    static final MobileIconGroup LTE_CA_5G_E = new MobileIconGroup(
+            "5Ge",
+            null,
+            null,
+            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH,
+            0, 0,
+            0,
+            0,
+            AccessibilityContentDescriptions.PHONE_SIGNAL_STRENGTH[0],
+            R.string.data_connection_5ge,
+            TelephonyIcons.ICON_5G_E,
+            true);
+
     static final MobileIconGroup NR_5G = new MobileIconGroup(
             "5G",
             null,
@@ -276,6 +290,7 @@
         ICON_NAME_TO_ICON.put("h+", H_PLUS);
         ICON_NAME_TO_ICON.put("4g", FOUR_G);
         ICON_NAME_TO_ICON.put("4g+", FOUR_G_PLUS);
+        ICON_NAME_TO_ICON.put("5ge", LTE_CA_5G_E);
         ICON_NAME_TO_ICON.put("lte", LTE);
         ICON_NAME_TO_ICON.put("lte+", LTE_PLUS);
         ICON_NAME_TO_ICON.put("5g", NR_5G);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/Events.java b/packages/SystemUI/src/com/android/systemui/volume/Events.java
index 2a84c5d..9bbfd22 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/Events.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/Events.java
@@ -94,7 +94,6 @@
     public static final int DISMISS_STREAM_GONE = 7;
     public static final int DISMISS_REASON_OUTPUT_CHOOSER = 8;
     public static final int DISMISS_REASON_USB_OVERHEAD_ALARM_CHANGED = 9;
-    public static final int DISMISS_REASON_ODI_CAPTIONS_CLICKED = 10;
     public static final String[] DISMISS_REASONS = {
             "unknown",
             "touch_outside",
@@ -105,8 +104,7 @@
             "done_clicked",
             "a11y_stream_changed",
             "output_chooser",
-            "usb_temperature_below_threshold",
-            "odi_captions_clicked"
+            "usb_temperature_below_threshold"
     };
 
     public static final int SHOW_REASON_UNKNOWN = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 2094b36..5095370 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -30,7 +30,6 @@
 import static android.view.View.VISIBLE;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.systemui.volume.Events.DISMISS_REASON_ODI_CAPTIONS_CLICKED;
 import static com.android.systemui.volume.Events.DISMISS_REASON_SETTINGS_CLICKED;
 
 import android.animation.ObjectAnimator;
@@ -519,7 +518,6 @@
             mODICaptionsIcon.setOnConfirmedTapListener(() -> {
                 onCaptionIconClicked();
                 Events.writeEvent(mContext, Events.EVENT_ODI_CAPTIONS_CLICK);
-                dismissH(DISMISS_REASON_ODI_CAPTIONS_CLICKED);
             }, mHandler);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
index f2ad958..17fbe09 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/clock/ClockManagerTest.java
@@ -18,12 +18,14 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ContentResolver;
 import android.database.ContentObserver;
+import android.net.Uri;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -52,6 +54,8 @@
 
     private static final String BUBBLE_CLOCK = BubbleClockController.class.getName();
     private static final Class<?> BUBBLE_CLOCK_CLASS = BubbleClockController.class;
+    private static final int USER_ID = 0;
+    private static final Uri SETTINGS_URI = null;
 
     private ClockManager mClockManager;
     private ContentObserver mContentObserver;
@@ -106,10 +110,10 @@
     @Test
     public void getCurrentClock_default() {
         // GIVEN that settings doesn't contain any values
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(null);
-        when(mMockSettingsWrapper.getDockedClockFace()).thenReturn(null);
+        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(null);
+        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(null);
         // WHEN settings change event is fired
-        mContentObserver.onChange(false);
+        mContentObserver.onChange(false, SETTINGS_URI, USER_ID);
         // THEN the result is null, indicated the default clock face should be used.
         assertThat(mClockManager.getCurrentClock()).isNull();
     }
@@ -117,9 +121,9 @@
     @Test
     public void getCurrentClock_customClock() {
         // GIVEN that settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK);
+        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
         // WHEN settings change event is fired
-        mContentObserver.onChange(false);
+        mContentObserver.onChange(false, SETTINGS_URI, USER_ID);
         // THEN the plugin is the bubble clock face.
         assertThat(mClockManager.getCurrentClock()).isInstanceOf(BUBBLE_CLOCK_CLASS);
     }
@@ -127,9 +131,9 @@
     @Test
     public void onClockChanged_customClock() {
         // GIVEN that settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK);
+        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
         // WHEN settings change event is fired
-        mContentObserver.onChange(false);
+        mContentObserver.onChange(false, SETTINGS_URI, USER_ID);
         // THEN the plugin is the bubble clock face.
         ArgumentCaptor<ClockPlugin> captor = ArgumentCaptor.forClass(ClockPlugin.class);
         verify(mMockListener1).onClockChanged(captor.capture());
@@ -139,9 +143,9 @@
     @Test
     public void onClockChanged_uniqueInstances() {
         // GIVEN that settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK);
+        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
         // WHEN settings change event is fired
-        mContentObserver.onChange(false);
+        mContentObserver.onChange(false, SETTINGS_URI, USER_ID);
         // THEN the listeners receive separate instances of the Bubble clock plugin.
         ArgumentCaptor<ClockPlugin> captor1 = ArgumentCaptor.forClass(ClockPlugin.class);
         ArgumentCaptor<ClockPlugin> captor2 = ArgumentCaptor.forClass(ClockPlugin.class);
@@ -156,9 +160,9 @@
     public void getCurrentClock_badSettingsValue() {
         // GIVEN that settings contains a value that doesn't correspond to a
         // custom clock face.
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn("bad value");
+        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn("bad value");
         // WHEN settings change event is fired
-        mContentObserver.onChange(false);
+        mContentObserver.onChange(false, SETTINGS_URI, USER_ID);
         // THEN the result is null.
         assertThat(mClockManager.getCurrentClock()).isNull();
     }
@@ -174,7 +178,7 @@
     @Test
     public void getCurrentClock_dockedCustomClock() {
         // GIVEN settings is set to the bubble clock face
-        when(mMockSettingsWrapper.getDockedClockFace()).thenReturn(BUBBLE_CLOCK);
+        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
         // WHEN dock event fires
         mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
         // THEN the plugin is the bubble clock face.
@@ -184,7 +188,7 @@
     @Test
     public void getCurrentClock_badDockedSettingsValue() {
         // GIVEN settings contains a value that doesn't correspond to an available clock face.
-        when(mMockSettingsWrapper.getDockedClockFace()).thenReturn("bad value");
+        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn("bad value");
         // WHEN dock event fires
         mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
         // THEN the result is null.
@@ -195,8 +199,8 @@
     public void getCurrentClock_badDockedSettingsFallback() {
         // GIVEN settings contains a value that doesn't correspond to an available clock face, but
         // locked screen settings is set to bubble clock.
-        when(mMockSettingsWrapper.getDockedClockFace()).thenReturn("bad value");
-        when(mMockSettingsWrapper.getLockScreenCustomClockFace()).thenReturn(BUBBLE_CLOCK);
+        when(mMockSettingsWrapper.getDockedClockFace(anyInt())).thenReturn("bad value");
+        when(mMockSettingsWrapper.getLockScreenCustomClockFace(anyInt())).thenReturn(BUBBLE_CLOCK);
         // WHEN dock event is fired
         mFakeDockManager.setDockEvent(DockManager.STATE_DOCKED);
         // THEN the plugin is the bubble clock face.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index ac6544e..0b53c48 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -300,7 +300,7 @@
         // We can only test whether unregister gets called if it thinks its in a listening
         // state.
         mNetworkController.mListening = true;
-        mNetworkController.setCurrentSubscriptions(subscriptions);
+        mNetworkController.setCurrentSubscriptionsLocked(subscriptions);
 
         for (int i = 0; i < testSubscriptions.length; i++) {
             if (i == indexToSkipController) {
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index c57d4e9..b9b3a61 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -18,6 +18,10 @@
 LOCAL_MODULE := frameworks-base-overlays
 LOCAL_REQUIRED_MODULES := \
 	AccentColorBlackOverlay \
+	AccentColorCinnamonOverlay \
+	AccentColorOceanOverlay \
+	AccentColorOrchidOverlay \
+	AccentColorSpaceOverlay \
 	AccentColorGreenOverlay \
 	AccentColorPurpleOverlay \
 	DisplayCutoutEmulationCornerOverlay \
diff --git a/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml
index 380ff34..4d844a1 100644
--- a/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml
+++ b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml
@@ -16,7 +16,7 @@
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
-    android:tint="@*android:color/accent_device_default"
+    android:tint="@*android:color/accent_device_default_light"
     android:viewportHeight="24"
     android:viewportWidth="24"
     android:width="24dp" >
diff --git a/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_settings_bluetooth.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_settings_bluetooth.xml
index 452a032..1973124 100644
--- a/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_settings_bluetooth.xml
+++ b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_settings_bluetooth.xml
@@ -16,6 +16,7 @@
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
     android:viewportHeight="24"
     android:viewportWidth="24"
     android:width="24dp" >
diff --git a/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml
index 8719f15..df79827 100644
--- a/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml
+++ b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml
@@ -16,7 +16,7 @@
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
-    android:tint="@*android:color/accent_device_default"
+    android:tint="@*android:color/accent_device_default_light"
     android:viewportHeight="24"
     android:viewportWidth="24"
     android:width="24dp" >
diff --git a/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_settings_bluetooth.xml b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_settings_bluetooth.xml
index 09643e6..58800c8 100644
--- a/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_settings_bluetooth.xml
+++ b/packages/overlays/IconPackFilledAndroidOverlay/res/drawable/ic_settings_bluetooth.xml
@@ -16,6 +16,7 @@
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
     android:viewportHeight="24"
     android:viewportWidth="24"
     android:width="24dp" >
diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml
index d0f9d9b..feed70c 100644
--- a/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml
+++ b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml
@@ -16,7 +16,7 @@
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
-    android:tint="@*android:color/accent_device_default"
+    android:tint="@*android:color/accent_device_default_light"
     android:viewportHeight="24"
     android:viewportWidth="24"
     android:width="24dp" >
diff --git a/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_settings_bluetooth.xml b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_settings_bluetooth.xml
index 3d270b3..5e1a5f2 100644
--- a/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_settings_bluetooth.xml
+++ b/packages/overlays/IconPackRoundedAndroidOverlay/res/drawable/ic_settings_bluetooth.xml
@@ -16,6 +16,7 @@
 -->
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
     android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
     android:viewportHeight="24"
     android:viewportWidth="24"
     android:width="24dp" >
diff --git a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
index 704ff2e..86fd47b 100644
--- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
+++ b/packages/overlays/NavigationBarModeGesturalOverlay/res/values/config.xml
@@ -33,4 +33,8 @@
     <!-- Controls the size of the back gesture inset. -->
     <dimen name="config_backGestureInset">20dp</dimen>
 
+    <!-- Controls whether the navbar needs a scrim with
+     {@link Window#setEnsureNavigationBarContrastWhenTransparent}. -->
+    <bool name="config_navBarNeedsScrim">false</bool>
+
 </resources>
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 0402b8f..fdc01e0 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -704,6 +704,7 @@
             mClient.asBinder().linkToDeath(mClientVulture, 0);
         } catch (RemoteException e) {
             Slog.w(TAG, "could not set binder death listener on autofill client: " + e);
+            mClientVulture = null;
         }
     }
 
@@ -714,6 +715,7 @@
             if (!unlinked) {
                 Slog.w(TAG, "unlinking vulture from death failed for " + mActivityToken);
             }
+            mClientVulture = null;
         }
     }
 
@@ -1243,18 +1245,55 @@
      * when necessary.
      */
     public void logContextCommitted() {
-        mHandler.sendMessage(obtainMessage(
-                Session::doLogContextCommitted, this));
+        mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this));
     }
 
-    private void doLogContextCommitted() {
+    private void handleLogContextCommitted() {
+        final FillResponse lastResponse;
         synchronized (mLock) {
-            logContextCommittedLocked();
+            lastResponse = getLastResponseLocked("logContextCommited()");
+        }
+
+        if (lastResponse == null) {
+            Slog.w(TAG, "handleLogContextCommitted(): last response is null");
+            return;
+        }
+
+        // Merge UserData if necessary.
+        // Fields in packageUserData will override corresponding fields in genericUserData.
+        final UserData genericUserData = mService.getUserData();
+        final UserData packageUserData = lastResponse.getUserData();
+        final FieldClassificationUserData userData;
+        if (packageUserData == null && genericUserData == null) {
+            userData = null;
+        } else if (packageUserData != null && genericUserData != null) {
+            userData = new CompositeUserData(genericUserData, packageUserData);
+        } else if (packageUserData != null) {
+            userData = packageUserData;
+        } else {
+            userData = mService.getUserData();
+        }
+
+        final FieldClassificationStrategy fcStrategy = mService.getFieldClassificationStrategy();
+
+        // Sets field classification scores
+        if (userData != null && fcStrategy != null) {
+            logFieldClassificationScore(fcStrategy, userData);
+        } else {
+            logContextCommitted(null, null);
+        }
+    }
+
+    private void logContextCommitted(@Nullable ArrayList<AutofillId> detectedFieldIds,
+            @Nullable ArrayList<FieldClassification> detectedFieldClassifications) {
+        synchronized (mLock) {
+            logContextCommittedLocked(detectedFieldIds, detectedFieldClassifications);
         }
     }
 
     @GuardedBy("mLock")
-    private void logContextCommittedLocked() {
+    private void logContextCommittedLocked(@Nullable ArrayList<AutofillId> detectedFieldIds,
+            @Nullable ArrayList<FieldClassification> detectedFieldClassifications) {
         final FillResponse lastResponse = getLastResponseLocked("logContextCommited()");
         if (lastResponse == null) return;
 
@@ -1308,21 +1347,6 @@
             return;
         }
 
-        // Merge UserData if necessary.
-        // Fields in packageUserData will override corresponding fields in genericUserData.
-        final UserData genericUserData = mService.getUserData();
-        final UserData packageUserData = lastResponse.getUserData();
-        final FieldClassificationUserData userData;
-        if (packageUserData == null && genericUserData == null) {
-            userData = null;
-        } else if (packageUserData != null && genericUserData != null) {
-            userData = new CompositeUserData(genericUserData, packageUserData);
-        } else if (packageUserData != null) {
-            userData = packageUserData;
-        } else {
-            userData = mService.getUserData();
-        }
-
         for (int i = 0; i < mViewStates.size(); i++) {
             final ViewState viewState = mViewStates.valueAt(i);
             final int state = viewState.getState();
@@ -1447,33 +1471,18 @@
             }
         }
 
-        // Sets field classification scores
-        final FieldClassificationStrategy fcStrategy = mService.getFieldClassificationStrategy();
-        if (userData != null && fcStrategy != null) {
-            logFieldClassificationScoreLocked(fcStrategy, ignoredDatasets, changedFieldIds,
-                    changedDatasetIds, manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                    userData, mViewStates.values());
-        } else {
-            mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
-                    ignoredDatasets, changedFieldIds, changedDatasetIds,
-                    manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                    mComponentName, mCompatMode);
-        }
+        mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
+                ignoredDatasets, changedFieldIds, changedDatasetIds,
+                manuallyFilledFieldIds, manuallyFilledDatasetIds, detectedFieldIds,
+                detectedFieldClassifications, mComponentName, mCompatMode);
     }
 
     /**
      * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for
      * {@code fieldId} based on its {@code currentValue} and {@code userData}.
      */
-    private void logFieldClassificationScoreLocked(
-            @NonNull FieldClassificationStrategy fcStrategy,
-            @NonNull ArraySet<String> ignoredDatasets,
-            @NonNull ArrayList<AutofillId> changedFieldIds,
-            @NonNull ArrayList<String> changedDatasetIds,
-            @NonNull ArrayList<AutofillId> manuallyFilledFieldIds,
-            @NonNull ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
-            @NonNull FieldClassificationUserData userData,
-            @NonNull Collection<ViewState> viewStates) {
+    private void logFieldClassificationScore(@NonNull FieldClassificationStrategy fcStrategy,
+            @NonNull FieldClassificationUserData userData) {
 
         final String[] userValues = userData.getValues();
         final String[] categoryIds = userData.getCategoryIds();
@@ -1499,6 +1508,11 @@
         final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>(
                 maxFieldsSize);
 
+        final Collection<ViewState> viewStates;
+        synchronized (mLock) {
+            viewStates = mViewStates.values();
+        }
+
         final int viewsSize = viewStates.size();
 
         // First, we get all scores.
@@ -1514,10 +1528,7 @@
         final RemoteCallback callback = new RemoteCallback((result) -> {
             if (result == null) {
                 if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
-                mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
-                        ignoredDatasets, changedFieldIds, changedDatasetIds,
-                        manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                        mComponentName, mCompatMode);
+                logContextCommitted(null, null);
                 return;
             }
             final Scores scores = result.getParcelable(EXTRA_SCORES);
@@ -1544,7 +1555,7 @@
                             final Float currentScore = scoresByField.get(categoryId);
                             if (currentScore != null && currentScore > score) {
                                 if (sVerbose) {
-                                    Slog.v(TAG,  "skipping score " + score
+                                    Slog.v(TAG, "skipping score " + score
                                             + " because it's less than " + currentScore);
                                 }
                                 continue;
@@ -1554,8 +1565,7 @@
                                         + autofillId);
                             }
                             scoresByField.put(categoryId, score);
-                        }
-                        else if (sVerbose) {
+                        } else if (sVerbose) {
                             Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId);
                         }
                     }
@@ -1579,10 +1589,7 @@
                 return;
             }
 
-            mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
-                    ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
-                    manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
-                    mComponentName, mCompatMode);
+            logContextCommitted(detectedFieldIds, detectedFieldClassifications);
         });
 
         fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds,
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index a3e7d36..54a3ecb 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -166,6 +166,9 @@
     @Override
     public void onUnlockUser(int userHandle) {
         Set<Association> associations = readAllAssociations(userHandle);
+        if (associations == null || associations.isEmpty()) {
+            return;
+        }
         Set<String> companionAppPackages = new HashSet<>();
         for (Association association : associations) {
             companionAppPackages.add(association.companionAppPackage);
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index 2cfcecc..2055b64 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -208,6 +208,7 @@
                     mBinder.linkToDeath(this, 0);
                 } catch (RemoteException e) {
                     binderDied();
+                    e.rethrowFromSystemServer();
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 8847e32..01a3a6f 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -1020,11 +1020,7 @@
                         if (state.state == STATE_RUNNING_UNLOCKED) {
                             // We'll skip all later code, so we must tell listener it's already
                             // unlocked.
-                            try {
-                                unlockListener.onFinished(userId, null);
-                            } catch (RemoteException ignore) {
-                                // Ignore.
-                            }
+                            notifyFinished(userId, unlockListener);
                         }
                         return true;
                     }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d073bc6..4c3bb8c 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1840,11 +1840,14 @@
     }
 
     private boolean isPackageSuspendedForUser(String pkg, int uid) {
+        final long identity = Binder.clearCallingIdentity();
         try {
             return AppGlobals.getPackageManager().isPackageSuspendedForUser(
                     pkg, UserHandle.getUserId(uid));
         } catch (RemoteException re) {
             throw new SecurityException("Could not talk to package manager service");
+        } finally {
+            Binder.restoreCallingIdentity(identity);
         }
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 39dae0b..d58888a 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1960,7 +1960,7 @@
             return;
         }
         for (final int groupedStream : avg.getLegacyStreamTypes()) {
-            setStreamVolume(stream, index, flags, callingPackage, callingPackage,
+            setStreamVolume(groupedStream, index, flags, callingPackage, callingPackage,
                             Binder.getCallingUid());
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 843ecac..153133a 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -41,6 +41,7 @@
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.biometrics.IBiometricConfirmDeviceCredentialCallback;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.IBiometricServiceReceiver;
@@ -85,6 +86,7 @@
 public class BiometricService extends SystemService {
 
     private static final String TAG = "BiometricService";
+    private static final boolean DEBUG = true;
 
     private static final int MSG_ON_TASK_STACK_CHANGED = 1;
     private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2;
@@ -96,6 +98,9 @@
     private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8;
     private static final int MSG_AUTHENTICATE = 9;
     private static final int MSG_CANCEL_AUTHENTICATION = 10;
+    private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS = 11;
+    private static final int MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR = 12;
+    private static final int MSG_REGISTER_CANCELLATION_CALLBACK = 13;
 
     private static final int[] FEATURE_ID = {
         TYPE_FINGERPRINT,
@@ -128,8 +133,12 @@
      * Authentication is successful, but we're waiting for the user to press "confirm" button.
      */
     private static final int STATE_AUTH_PENDING_CONFIRM = 5;
+    /**
+     * Biometric authentication was canceled, but the device is now showing ConfirmDeviceCredential
+     */
+    private static final int STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC = 6;
 
-    private final class AuthSession {
+    private final class AuthSession implements IBinder.DeathRecipient {
         // Map of Authenticator/Cookie pairs. We expect to receive the cookies back from
         // <Biometric>Services before we can start authenticating. Pairs that have been returned
         // are moved to mModalitiesMatched.
@@ -164,10 +173,14 @@
         // Timestamp when hardware authentication occurred
         private long mAuthenticatedTimeMs;
 
+        // TODO(b/123378871): Remove when moved.
+        private IBiometricConfirmDeviceCredentialCallback mConfirmDeviceCredentialCallback;
+
         AuthSession(HashMap<Integer, Integer> modalities, IBinder token, long sessionId,
                 int userId, IBiometricServiceReceiver receiver, String opPackageName,
                 Bundle bundle, int callingUid, int callingPid, int callingUserId,
-                int modality, boolean requireConfirmation) {
+                int modality, boolean requireConfirmation,
+                IBiometricConfirmDeviceCredentialCallback callback) {
             mModalitiesWaiting = modalities;
             mToken = token;
             mSessionId = sessionId;
@@ -180,12 +193,25 @@
             mCallingUserId = callingUserId;
             mModality = modality;
             mRequireConfirmation = requireConfirmation;
+            mConfirmDeviceCredentialCallback = callback;
+
+            if (isFromConfirmDeviceCredential()) {
+                try {
+                    token.linkToDeath(this, 0 /* flags */);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to link to death", e);
+                }
+            }
         }
 
         boolean isCrypto() {
             return mSessionId != 0;
         }
 
+        boolean isFromConfirmDeviceCredential() {
+            return mBundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false);
+        }
+
         boolean containsCookie(int cookie) {
             if (mModalitiesWaiting != null && mModalitiesWaiting.containsValue(cookie)) {
                 return true;
@@ -195,6 +221,25 @@
             }
             return false;
         }
+
+        // TODO(b/123378871): Remove when moved.
+        @Override
+        public void binderDied() {
+            mHandler.post(() -> {
+                Slog.e(TAG, "Binder died, killing ConfirmDeviceCredential");
+                if (mConfirmDeviceCredentialCallback == null) {
+                    Slog.e(TAG, "Callback is null");
+                    return;
+                }
+
+                try {
+                    mConfirmDeviceCredentialCallback.cancel();
+                    mConfirmDeviceCredentialCallback = null;
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Unable to send cancel", e);
+                }
+            });
+        }
     }
 
     private final class BiometricTaskStackListener extends TaskStackListener {
@@ -234,6 +279,14 @@
     private AuthSession mCurrentAuthSession;
     private AuthSession mPendingAuthSession;
 
+    // TODO(b/123378871): Remove when moved.
+    // When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the
+    // client (app) receiver. BiometricService internally launches CDCA which invokes
+    // BiometricService to start authentication (normal path). When auth is success/rejected,
+    // CDCA will use an aidl method to poke BiometricService - the result will then be forwarded
+    // to this receiver.
+    private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver;
+
     private final Handler mHandler = new Handler(Looper.getMainLooper()) {
         @Override
         public void handleMessage(Message msg) {
@@ -311,7 +364,8 @@
                             (Bundle) args.arg5 /* bundle */,
                             args.argi2 /* callingUid */,
                             args.argi3 /* callingPid */,
-                            args.argi4 /* callingUserId */);
+                            args.argi4 /* callingUserId */,
+                            (IBiometricConfirmDeviceCredentialCallback) args.arg6 /* callback */);
                     args.recycle();
                     break;
                 }
@@ -325,7 +379,28 @@
                     break;
                 }
 
+                case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS: {
+                    handleOnConfirmDeviceCredentialSuccess();
+                    break;
+                }
+
+                case MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR: {
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    handleOnConfirmDeviceCredentialError(
+                            args.argi1 /* error */,
+                            (String) args.arg1 /* errorMsg */);
+                    args.recycle();
+                    break;
+                }
+
+                case MSG_REGISTER_CANCELLATION_CALLBACK: {
+                    handleRegisterCancellationCallback(
+                            (IBiometricConfirmDeviceCredentialCallback) msg.obj /* callback */);
+                    break;
+                }
+
                 default:
+                    Slog.e(TAG, "Unknown message: " + msg);
                     break;
             }
         }
@@ -533,14 +608,6 @@
      * cancelAuthentication() can go to the right place.
      */
     private final class BiometricServiceWrapper extends IBiometricService.Stub {
-        // TODO(b/123378871): Remove when moved.
-        // When BiometricPrompt#setAllowDeviceCredentials is set to true, we need to store the
-        // client (app) receiver. BiometricService internally launches CDCA which invokes
-        // BiometricService to start authentication (normal path). When auth is success/rejected,
-        // CDCA will use an aidl method to poke BiometricService - the result will then be forwarded
-        // to this receiver.
-        private IBiometricServiceReceiver mConfirmDeviceCredentialReceiver;
-
         @Override // Binder call
         public void onReadyForAuthentication(int cookie, boolean requireConfirmation, int userId) {
             checkInternalPermission();
@@ -554,12 +621,18 @@
 
         @Override // Binder call
         public void authenticate(IBinder token, long sessionId, int userId,
-                IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle)
+                IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
+                IBiometricConfirmDeviceCredentialCallback callback)
                 throws RemoteException {
             final int callingUid = Binder.getCallingUid();
             final int callingPid = Binder.getCallingPid();
             final int callingUserId = UserHandle.getCallingUserId();
 
+            // TODO(b/123378871): Remove when moved.
+            if (callback != null) {
+                checkInternalPermission();
+            }
+
             // In the BiometricServiceBase, check do the AppOps and foreground check.
             if (userId == callingUserId) {
                 // Check the USE_BIOMETRIC permission here.
@@ -576,6 +649,12 @@
                 return;
             }
 
+            final boolean isFromConfirmDeviceCredential =
+                    bundle.getBoolean(BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false);
+            if (isFromConfirmDeviceCredential) {
+                checkInternalPermission();
+            }
+
             // Check the usage of this in system server. Need to remove this check if it becomes
             // a public API.
             final boolean useDefaultTitle =
@@ -632,6 +711,7 @@
             args.argi2 = callingUid;
             args.argi3 = callingPid;
             args.argi4 = callingUserId;
+            args.arg6 = callback;
 
             mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget();
         }
@@ -639,35 +719,30 @@
         @Override // Binder call
         public void onConfirmDeviceCredentialSuccess() {
             checkInternalPermission();
-            mHandler.post(() -> {
-                if (mConfirmDeviceCredentialReceiver == null) {
-                    Slog.w(TAG, "onCDCASuccess null!");
-                    return;
-                }
-                try {
-                    mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded();
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "RemoteException", e);
-                }
-                mConfirmDeviceCredentialReceiver = null;
-            });
+
+            mHandler.sendEmptyMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_SUCCESS);
         }
 
         @Override // Binder call
         public void onConfirmDeviceCredentialError(int error, String message) {
             checkInternalPermission();
-            mHandler.post(() -> {
-                if (mConfirmDeviceCredentialReceiver == null) {
-                    Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message);
-                    return;
-                }
-                try {
-                    mConfirmDeviceCredentialReceiver.onError(error, message);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "RemoteException", e);
-                }
-                mConfirmDeviceCredentialReceiver = null;
-            });
+
+            SomeArgs args = SomeArgs.obtain();
+            args.argi1 = error;
+            args.arg1 = message;
+            mHandler.obtainMessage(MSG_ON_CONFIRM_DEVICE_CREDENTIAL_ERROR, args).sendToTarget();
+        }
+
+        @Override // Binder call
+        public void registerCancellationCallback(
+                IBiometricConfirmDeviceCredentialCallback callback) {
+            // TODO(b/123378871): Remove when moved.
+            // This callback replaces the one stored in the current session. If the session is null
+            // we can ignore this, since it means ConfirmDeviceCredential was launched by something
+            // else (not BiometricPrompt)
+            checkInternalPermission();
+
+            mHandler.obtainMessage(MSG_REGISTER_CANCELLATION_CALLBACK, callback).sendToTarget();
         }
 
         @Override // Binder call
@@ -1104,6 +1179,52 @@
         }
     }
 
+    private void handleOnConfirmDeviceCredentialSuccess() {
+        if (mConfirmDeviceCredentialReceiver == null) {
+            Slog.w(TAG, "onCDCASuccess null!");
+            return;
+        }
+        try {
+            mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+            mConfirmDeviceCredentialReceiver.onAuthenticationSucceeded();
+            if (mCurrentAuthSession != null) {
+                mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                mCurrentAuthSession = null;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "RemoteException", e);
+        }
+        mConfirmDeviceCredentialReceiver = null;
+    }
+
+    private void handleOnConfirmDeviceCredentialError(int error, String message) {
+        if (mConfirmDeviceCredentialReceiver == null) {
+            Slog.w(TAG, "onCDCAError null! Error: " + error + " " + message);
+            return;
+        }
+        try {
+            mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+            mConfirmDeviceCredentialReceiver.onError(error, message);
+            if (mCurrentAuthSession != null) {
+                mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                mCurrentAuthSession = null;
+            }
+        } catch (RemoteException e) {
+            Slog.e(TAG, "RemoteException", e);
+        }
+        mConfirmDeviceCredentialReceiver = null;
+    }
+
+    private void handleRegisterCancellationCallback(
+            IBiometricConfirmDeviceCredentialCallback callback) {
+        if (mCurrentAuthSession == null) {
+            Slog.d(TAG, "Current auth session null");
+            return;
+        }
+        Slog.d(TAG, "Updating cancel callback");
+        mCurrentAuthSession.mConfirmDeviceCredentialCallback = callback;
+    }
+
     private void handleOnError(int cookie, int error, String message) {
         Slog.d(TAG, "Error: " + error + " cookie: " + cookie);
         // Errors can either be from the current auth session or the pending auth session.
@@ -1114,7 +1235,18 @@
         // of their intended receivers.
         try {
             if (mCurrentAuthSession != null && mCurrentAuthSession.containsCookie(cookie)) {
-                if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
+
+                if (mCurrentAuthSession.isFromConfirmDeviceCredential()) {
+                    // If we were invoked by ConfirmDeviceCredential, do not delete the current
+                    // auth session since we still need to respond to cancel signal while
+                    if (DEBUG) Slog.d(TAG, "From CDC, transition to CANCELED_SHOWING_CDC state");
+
+                    // Send the error to ConfirmDeviceCredential so that it goes to Pin/Pattern/Pass
+                    // screen
+                    mCurrentAuthSession.mClientReceiver.onError(error, message);
+                    mCurrentAuthSession.mState = STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC;
+                    mStatusBarService.hideBiometricDialog();
+                } else if (mCurrentAuthSession.mState == STATE_AUTH_STARTED) {
                     mStatusBarService.onBiometricError(message);
                     if (error == BiometricConstants.BIOMETRIC_ERROR_CANCELED) {
                         mActivityTaskManager.unregisterTaskStackListener(
@@ -1214,9 +1346,16 @@
                 KeyStore.getInstance().addAuthToken(mCurrentAuthSession.mTokenEscrow);
                 mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
             }
-            mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
-            mCurrentAuthSession.mState = STATE_AUTH_IDLE;
-            mCurrentAuthSession = null;
+
+            // Do not clean up yet if we are from ConfirmDeviceCredential. We should be in the
+            // STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC. The session should only be removed when
+            // ConfirmDeviceCredential is confirmed or canceled.
+            // TODO(b/123378871): Remove when moved
+            if (!mCurrentAuthSession.isFromConfirmDeviceCredential()) {
+                mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+                mCurrentAuthSession.mState = STATE_AUTH_IDLE;
+                mCurrentAuthSession = null;
+            }
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception", e);
         }
@@ -1235,7 +1374,8 @@
                 mCurrentAuthSession.mCallingUid,
                 mCurrentAuthSession.mCallingPid,
                 mCurrentAuthSession.mCallingUserId,
-                mCurrentAuthSession.mModality);
+                mCurrentAuthSession.mModality,
+                mCurrentAuthSession.mConfirmDeviceCredentialCallback);
     }
 
     private void handleOnReadyForAuthentication(int cookie, boolean requireConfirmation,
@@ -1290,7 +1430,8 @@
 
     private void handleAuthenticate(IBinder token, long sessionId, int userId,
             IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
-            int callingUid, int callingPid, int callingUserId) {
+            int callingUid, int callingPid, int callingUserId,
+            IBiometricConfirmDeviceCredentialCallback callback) {
 
         mHandler.post(() -> {
             final Pair<Integer, Integer> result = checkAndGetBiometricModality(userId);
@@ -1328,7 +1469,7 @@
             // Start preparing for authentication. Authentication starts when
             // all modalities requested have invoked onReadyForAuthentication.
             authenticateInternal(token, sessionId, userId, receiver, opPackageName, bundle,
-                    callingUid, callingPid, callingUserId, modality);
+                    callingUid, callingPid, callingUserId, modality, callback);
         });
     }
 
@@ -1343,7 +1484,8 @@
      */
     private void authenticateInternal(IBinder token, long sessionId, int userId,
             IBiometricServiceReceiver receiver, String opPackageName, Bundle bundle,
-            int callingUid, int callingPid, int callingUserId, int modality) {
+            int callingUid, int callingPid, int callingUserId, int modality,
+            IBiometricConfirmDeviceCredentialCallback callback) {
         try {
             boolean requireConfirmation = bundle.getBoolean(
                     BiometricPrompt.KEY_REQUIRE_CONFIRMATION, true /* default */);
@@ -1363,7 +1505,7 @@
             authenticators.put(modality, cookie);
             mPendingAuthSession = new AuthSession(authenticators, token, sessionId, userId,
                     receiver, opPackageName, bundle, callingUid, callingPid, callingUserId,
-                    modality, requireConfirmation);
+                    modality, requireConfirmation, callback);
             mPendingAuthSession.mState = STATE_AUTH_CALLED;
             // No polymorphism :(
             if ((modality & TYPE_FINGERPRINT) != 0) {
@@ -1390,10 +1532,23 @@
             return;
         }
 
-        // We need to check the current authenticators state. If we're pending confirm
-        // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client,
-        // since we won't be getting an onError from the driver.
-        if (mCurrentAuthSession != null && mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+        if (mCurrentAuthSession != null
+                && mCurrentAuthSession.mState == STATE_BIOMETRIC_AUTH_CANCELED_SHOWING_CDC) {
+            if (DEBUG) Slog.d(TAG, "Cancel received while ConfirmDeviceCredential showing");
+            try {
+                mCurrentAuthSession.mConfirmDeviceCredentialCallback.cancel();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Unable to cancel ConfirmDeviceCredential", e);
+            }
+
+            // TODO(b/123378871): Remove when moved. Piggy back on this for now to clean up.
+            handleOnConfirmDeviceCredentialError(BiometricConstants.BIOMETRIC_ERROR_CANCELED,
+                    getContext().getString(R.string.biometric_error_canceled));
+        } else if (mCurrentAuthSession != null
+                && mCurrentAuthSession.mState != STATE_AUTH_STARTED) {
+            // We need to check the current authenticators state. If we're pending confirm
+            // or idle, we need to dismiss the dialog and send an ERROR_CANCELED to the client,
+            // since we won't be getting an onError from the driver.
             try {
                 // Send error to client
                 mCurrentAuthSession.mClientReceiver.onError(
@@ -1409,11 +1564,22 @@
                 Slog.e(TAG, "Remote exception", e);
             }
         } else {
-            cancelInternal(token, opPackageName, true /* fromClient */);
+            boolean fromCDC = false;
+            if (mCurrentAuthSession != null) {
+                fromCDC = mCurrentAuthSession.mBundle.getBoolean(
+                        BiometricPrompt.KEY_FROM_CONFIRM_DEVICE_CREDENTIAL, false);
+            }
+
+            if (fromCDC) {
+                if (DEBUG) Slog.d(TAG, "Cancelling from CDC");
+                cancelInternal(token, opPackageName, false /* fromClient */);
+            } else {
+                cancelInternal(token, opPackageName, true /* fromClient */);
+            }
+
         }
     }
 
-
     void cancelInternal(IBinder token, String opPackageName, boolean fromClient) {
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 527539d..7733d67 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -50,7 +50,7 @@
 import java.util.Set;
 
 /**
- * CameraServiceProxy is the system_server analog to the camera service running in mediaserver.
+ * CameraServiceProxy is the system_server analog to the camera service running in cameraserver.
  *
  * @hide
  */
@@ -74,6 +74,7 @@
     private static final int MSG_SWITCH_USER = 1;
 
     private static final int RETRY_DELAY_TIME = 20; //ms
+    private static final int RETRY_TIMES = 30;
 
     // Maximum entries to keep in usage history before dumping out
     private static final int MAX_USAGE_HISTORY = 100;
@@ -171,7 +172,7 @@
                         " camera service UID!");
                 return;
             }
-            notifySwitchWithRetries(30);
+            notifySwitchWithRetries(RETRY_TIMES);
         }
 
         @Override
@@ -242,7 +243,8 @@
     public void onStartUser(int userHandle) {
         synchronized(mLock) {
             if (mEnabledCameraUsers == null) {
-                // Initialize mediaserver, or update mediaserver if we are recovering from a crash.
+                // Initialize cameraserver, or update cameraserver if we are recovering
+                // from a crash.
                 switchUserLocked(userHandle);
             }
         }
@@ -324,9 +326,9 @@
         Set<Integer> currentUserHandles = getEnabledUserHandles(userHandle);
         mLastUser = userHandle;
         if (mEnabledCameraUsers == null || !mEnabledCameraUsers.equals(currentUserHandles)) {
-            // Some user handles have been added or removed, update mediaserver.
+            // Some user handles have been added or removed, update cameraserver.
             mEnabledCameraUsers = currentUserHandles;
-            notifyMediaserverLocked(ICameraService.EVENT_USER_SWITCHED, currentUserHandles);
+            notifySwitchWithRetriesLocked(RETRY_TIMES);
         }
     }
 
@@ -343,12 +345,16 @@
 
     private void notifySwitchWithRetries(int retries) {
         synchronized(mLock) {
-            if (mEnabledCameraUsers == null) {
-                return;
-            }
-            if (notifyMediaserverLocked(ICameraService.EVENT_USER_SWITCHED, mEnabledCameraUsers)) {
-                retries = 0;
-            }
+            notifySwitchWithRetriesLocked(retries);
+        }
+    }
+
+    private void notifySwitchWithRetriesLocked(int retries) {
+        if (mEnabledCameraUsers == null) {
+            return;
+        }
+        if (notifyCameraserverLocked(ICameraService.EVENT_USER_SWITCHED, mEnabledCameraUsers)) {
+            retries = 0;
         }
         if (retries <= 0) {
             return;
@@ -358,13 +364,13 @@
                 RETRY_DELAY_TIME);
     }
 
-    private boolean notifyMediaserverLocked(int eventType, Set<Integer> updatedUserHandles) {
-        // Forward the user switch event to the native camera service running in the mediaserver
+    private boolean notifyCameraserverLocked(int eventType, Set<Integer> updatedUserHandles) {
+        // Forward the user switch event to the native camera service running in the cameraserver
         // process.
         if (mCameraServiceRaw == null) {
             IBinder cameraServiceBinder = getBinderService(CAMERA_SERVICE_BINDER_NAME);
             if (cameraServiceBinder == null) {
-                Slog.w(TAG, "Could not notify mediaserver, camera service not available.");
+                Slog.w(TAG, "Could not notify cameraserver, camera service not available.");
                 return false; // Camera service not active, cannot evict user clients.
             }
             try {
@@ -380,7 +386,7 @@
         try {
             mCameraServiceRaw.notifySystemEvent(eventType, toArray(updatedUserHandles));
         } catch (RemoteException e) {
-            Slog.w(TAG, "Could not notify mediaserver, remote exception: " + e);
+            Slog.w(TAG, "Could not notify cameraserver, remote exception: " + e);
             // Not much we can do if camera service is dead.
             return false;
         }
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 9590f81..a7d0a5c 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -461,7 +461,9 @@
                     .setMatrix(mNightDisplayTintController.getColorTemperatureSetting());
         }
 
-        updateDisplayWhiteBalanceStatus();
+        if (mDisplayWhiteBalanceTintController.isAvailable(getContext())) {
+            updateDisplayWhiteBalanceStatus();
+        }
 
         final DisplayTransformManager dtm = getLocalService(DisplayTransformManager.class);
         dtm.setColorMode(mode, mNightDisplayTintController.getMatrix());
@@ -624,7 +626,11 @@
             return false;
         }
         return Secure.getIntForUser(getContext().getContentResolver(),
-                Secure.DISPLAY_WHITE_BALANCE_ENABLED, 0, mCurrentUser) == 1;
+                Secure.DISPLAY_WHITE_BALANCE_ENABLED,
+                getContext().getResources()
+                        .getBoolean(R.bool.config_displayWhiteBalanceEnabledDefault) ? 1
+                        : 0,
+                mCurrentUser) == 1;
     }
 
     private boolean isDeviceColorManagedInternal() {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 6f1929f..d360a63 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4101,13 +4101,15 @@
 
     // ----------------------------------------------------------------------
 
-    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
-        // Make sure this is a valid input method.
-        InputMethodInfo imm = mMethodMap.get(id);
-        if (imm == null) {
-            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
-        }
-
+    /**
+     * Enable or disable the given IME by updating {@link Settings.Secure#ENABLED_INPUT_METHODS}.
+     *
+     * @param id ID of the IME is to be manipulated. It is OK to pass IME ID that is currently not
+     *           recognized by the system.
+     * @param enabled {@code true} if {@code id} needs to be enabled.
+     * @return {@code true} if the IME was previously enabled. {@code false} otherwise.
+     */
+    private boolean setInputMethodEnabledLocked(String id, boolean enabled) {
         List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
                 .getEnabledInputMethodsAndSubtypeListLocked();
 
@@ -4697,6 +4699,14 @@
             if (!userHasDebugPriv(mSettings.getCurrentUserId(), shellCommand)) {
                 return ShellCommandResult.SUCCESS;
             }
+            // Make sure this is a valid input method.
+            if (enabled && !mMethodMap.containsKey(id)) {
+                final PrintWriter error = shellCommand.getErrPrintWriter();
+                error.print("Unknown input method ");
+                error.print(id);
+                error.println(" cannot be enabled");
+                return ShellCommandResult.SUCCESS;
+            }
             previouslyEnabled = setInputMethodEnabledLocked(id, enabled);
         }
         final PrintWriter pr = shellCommand.getOutPrintWriter();
diff --git a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
index 0cabf1d..de36dea 100644
--- a/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaSessionServiceImpl.java
@@ -1489,8 +1489,10 @@
             final long token = Binder.clearCallingIdentity();
 
             if (DEBUG_KEY_EVENT) {
-                Log.d(TAG, "dispatchVolumeKeyEvent, pkg=" + packageName + ", pid=" + pid + ", uid="
-                        + uid + ", asSystem=" + asSystemService + ", event=" + keyEvent);
+                Log.d(TAG, "dispatchVolumeKeyEvent, pkg=" + packageName
+                        + ", opPkg=" + opPackageName + ", pid=" + pid + ", uid=" + uid
+                        + ", asSystem=" + asSystemService + ", event=" + keyEvent
+                        + ", stream=" + stream + ", musicOnly=" + musicOnly);
             }
 
             try {
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index 1559911..f34ace5 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -25,6 +25,7 @@
 import static android.content.Intent.EXTRA_UID;
 import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
 import static android.net.ConnectivityManager.isNetworkTypeMobile;
+import static android.net.NetworkStack.checkNetworkStackPermission;
 import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
 import static android.net.NetworkStats.IFACE_ALL;
 import static android.net.NetworkStats.INTERFACES_ALL;
@@ -866,7 +867,7 @@
             VpnInfo[] vpnArray,
             NetworkState[] networkStates,
             String activeIface) {
-        mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
+        checkNetworkStackPermission(mContext);
         assertBandwidthControlEnabled();
 
         final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 0488d3a..4a6eb27 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -79,7 +79,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
-import java.util.function.Predicate;
 
 /**
  * Manages the lifecycle of application-provided services bound by system server.
@@ -1163,6 +1162,7 @@
                 @Override
                 public void onNullBinding(ComponentName name) {
                     Slog.v(TAG, "onNullBinding() called with: name = [" + name + "]");
+                    mServicesBound.remove(servicesBindingTag);
                 }
             };
             if (!mContext.bindServiceAsUser(intent,
@@ -1180,6 +1180,11 @@
         }
     }
 
+    boolean isBound(ComponentName cn, int userId) {
+        final Pair<ComponentName, Integer> servicesBindingTag = Pair.create(cn, userId);
+        return mServicesBound.contains(servicesBindingTag);
+    }
+
     /**
      * Remove a service for the given user by ComponentName
      */
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index dec47a1..7f1b25ca 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1516,6 +1516,11 @@
     }
 
     @VisibleForTesting
+    void setZenHelper(ZenModeHelper zenHelper) {
+        mZenModeHelper = zenHelper;
+    }
+
+    @VisibleForTesting
     void setIsAutomotive(boolean isAutomotive) {
         mIsAutomotive = isAutomotive;
     }
@@ -2856,7 +2861,7 @@
         }
 
         @Override
-        public List<String> getAllowedAssistantCapabilities(String pkg) {
+        public List<String> getAllowedAssistantAdjustments(String pkg) {
             checkCallerIsSystemOrSameApp(pkg);
 
             if (!isCallerSystemOrPhone()
@@ -2864,11 +2869,11 @@
                     throw new SecurityException("Not currently an assistant");
             }
 
-            return mAssistants.getAllowedAssistantCapabilities();
+            return mAssistants.getAllowedAssistantAdjustments();
         }
 
         @Override
-        public void allowAssistantCapability(String adjustmentType) {
+        public void allowAssistantAdjustment(String adjustmentType) {
             checkCallerIsSystemOrSystemUiOrShell();
             mAssistants.allowAdjustmentType(adjustmentType);
 
@@ -2876,7 +2881,7 @@
         }
 
         @Override
-        public void disallowAssistantCapability(String adjustmentType) {
+        public void disallowAssistantAdjustment(String adjustmentType) {
             checkCallerIsSystemOrSystemUiOrShell();
             mAssistants.disallowAdjustmentType(adjustmentType);
 
@@ -3563,7 +3568,7 @@
                 return;
             }
             boolean accessAllowed = false;
-            String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
+            String[] packages = mPackageManagerClient.getPackagesForUid(uid);
             final int packageCount = packages.length;
             for (int i = 0; i < packageCount; i++) {
                 if (mConditionProviders.isPackageOrComponentAllowed(
@@ -7410,7 +7415,7 @@
             }
         }
 
-        protected List<String> getAllowedAssistantCapabilities() {
+        protected List<String> getAllowedAssistantAdjustments() {
             synchronized (mLock) {
                 List<String> types = new ArrayList<>();
                 types.addAll(mAllowedAdjustments);
@@ -7470,7 +7475,7 @@
         private void notifyCapabilitiesChanged(final ManagedServiceInfo info) {
             final INotificationListener assistant = (INotificationListener) info.service;
             try {
-                assistant.onCapabilitiesChanged();
+                assistant.onAllowedAdjustmentsChanged();
             } catch (RemoteException ex) {
                 Slog.e(TAG, "unable to notify assistant (capabilities): " + assistant, ex);
             }
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index ea7bf2d2..7e74cc2 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -389,8 +389,8 @@
             if (mConfig == null) return;
 
             newConfig = mConfig.copy();
+            setAutomaticZenRuleStateLocked(newConfig, newConfig.automaticRules.get(id), condition);
         }
-        setAutomaticZenRuleState(newConfig, newConfig.automaticRules.get(id), condition);
     }
 
     public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition) {
@@ -398,14 +398,15 @@
         synchronized (mConfig) {
             if (mConfig == null) return;
             newConfig = mConfig.copy();
-        }
 
-        setAutomaticZenRuleState(newConfig,
-                findMatchingRule(newConfig, ruleDefinition, condition),
-                condition);
+            setAutomaticZenRuleStateLocked(newConfig,
+                    findMatchingRule(newConfig, ruleDefinition, condition),
+                    condition);
+        }
     }
 
-    private void setAutomaticZenRuleState(ZenModeConfig config, ZenRule rule, Condition condition) {
+    private void setAutomaticZenRuleStateLocked(ZenModeConfig config, ZenRule rule,
+            Condition condition) {
         if (rule == null) return;
 
         rule.condition = condition;
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 944aef5..21b6f12 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -324,6 +324,8 @@
                     ipw.println("State: ROLLBACK IN PROGRESS");
                 } else if (si.isRolledBack) {
                     ipw.println("State: ROLLED BACK");
+                } else if (si.isRollbackFailed) {
+                    ipw.println("State: ROLLBACK FAILED");
                 }
                 ipw.decreaseIndent();
             }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 898437d..6c5abe4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -757,124 +757,9 @@
         @Override public final boolean hasFeature(String feature) {
             return PackageManagerService.this.hasSystemFeature(feature, 0);
         }
-
-        final List<PackageParser.Package> getStaticOverlayPackages(
-                Collection<PackageParser.Package> allPackages, String targetPackageName) {
-            if ("android".equals(targetPackageName)) {
-                // Static RROs targeting to "android", ie framework-res.apk, are already applied by
-                // native AssetManager.
-                return null;
-            }
-
-            List<PackageParser.Package> overlayPackages = null;
-            for (PackageParser.Package p : allPackages) {
-                if (targetPackageName.equals(p.mOverlayTarget) && p.mOverlayIsStatic) {
-                    if (overlayPackages == null) {
-                        overlayPackages = new ArrayList<>();
-                    }
-                    overlayPackages.add(p);
-                }
-            }
-            if (overlayPackages != null) {
-                Comparator<PackageParser.Package> cmp =
-                        Comparator.comparingInt(p -> p.mOverlayPriority);
-                overlayPackages.sort(cmp);
-            }
-            return overlayPackages;
-        }
-
-        final String[] getStaticOverlayPaths(List<PackageParser.Package> overlayPackages,
-                String targetPath) {
-            if (overlayPackages == null || overlayPackages.isEmpty()) {
-                return null;
-            }
-            List<String> overlayPathList = null;
-            for (PackageParser.Package overlayPackage : overlayPackages) {
-                if (targetPath == null) {
-                    if (overlayPathList == null) {
-                        overlayPathList = new ArrayList<>();
-                    }
-                    overlayPathList.add(overlayPackage.baseCodePath);
-                    continue;
-                }
-
-                try {
-                    // Creates idmaps for system to parse correctly the Android manifest of the
-                    // target package.
-                    //
-                    // OverlayManagerService will update each of them with a correct gid from its
-                    // target package app id.
-                    mInstaller.idmap(targetPath, overlayPackage.baseCodePath,
-                            UserHandle.getSharedAppGid(
-                                    UserHandle.getUserGid(UserHandle.USER_SYSTEM)));
-                    if (overlayPathList == null) {
-                        overlayPathList = new ArrayList<>();
-                    }
-                    overlayPathList.add(overlayPackage.baseCodePath);
-                } catch (InstallerException e) {
-                    Slog.e(TAG, "Failed to generate idmap for " + targetPath + " and " +
-                            overlayPackage.baseCodePath);
-                }
-            }
-            return overlayPathList == null ? null : overlayPathList.toArray(new String[0]);
-        }
-
-        String[] getStaticOverlayPaths(String targetPackageName, String targetPath) {
-            List<PackageParser.Package> overlayPackages;
-            synchronized (mInstallLock) {
-                synchronized (mPackages) {
-                    overlayPackages = getStaticOverlayPackages(
-                            mPackages.values(), targetPackageName);
-                }
-                // It is safe to keep overlayPackages without holding mPackages because static overlay
-                // packages can't be uninstalled or disabled.
-                return getStaticOverlayPaths(overlayPackages, targetPath);
-            }
-        }
-
-        @Override public final String[] getOverlayApks(String targetPackageName) {
-            return getStaticOverlayPaths(targetPackageName, null);
-        }
-
-        @Override public final String[] getOverlayPaths(String targetPackageName,
-                String targetPath) {
-            return getStaticOverlayPaths(targetPackageName, targetPath);
-        }
-    }
-
-    class ParallelPackageParserCallback extends PackageParserCallback {
-        List<PackageParser.Package> mOverlayPackages = null;
-
-        void findStaticOverlayPackages() {
-            synchronized (mPackages) {
-                for (PackageParser.Package p : mPackages.values()) {
-                    if (p.mOverlayIsStatic) {
-                        if (mOverlayPackages == null) {
-                            mOverlayPackages = new ArrayList<>();
-                        }
-                        mOverlayPackages.add(p);
-                    }
-                }
-            }
-        }
-
-        @Override
-        synchronized String[] getStaticOverlayPaths(String targetPackageName, String targetPath) {
-            // We can trust mOverlayPackages without holding mPackages because package uninstall
-            // can't happen while running parallel parsing.
-            // And we can call mInstaller inside getStaticOverlayPaths without holding mInstallLock
-            // because mInstallLock is held before running parallel parsing.
-            // Moreover holding mPackages or mInstallLock on each parsing thread causes dead-lock.
-            return mOverlayPackages == null ? null :
-                    getStaticOverlayPaths(
-                            getStaticOverlayPackages(mOverlayPackages, targetPackageName),
-                            targetPath);
-        }
     }
 
     final PackageParser.Callback mPackageParserCallback = new PackageParserCallback();
-    final ParallelPackageParserCallback mParallelPackageParserCallback =
-            new ParallelPackageParserCallback();
 
     // Currently known shared libraries.
     final ArrayMap<String, LongSparseArray<SharedLibraryInfo>> mSharedLibraries = new ArrayMap<>();
@@ -2558,8 +2443,6 @@
                     | SCAN_AS_ODM,
                     0);
 
-            mParallelPackageParserCallback.findStaticOverlayPackages();
-
             // Find base frameworks (resource packages without code).
             scanDirTracedLI(frameworkDir,
                     mDefParseFlags
@@ -8782,7 +8665,7 @@
         }
         try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
                 mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
-                mParallelPackageParserCallback)) {
+                mPackageParserCallback)) {
             // Submit files for parsing in parallel
             int fileCount = 0;
             for (File file : files) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index a0f0a31..1908b3f 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -565,7 +565,8 @@
         // isRollbackInProgress is included to cover the scenario, when a device is rebooted in
         // during the rollback, and apexd fails to resume the rollback after reboot.
         return apexSessionInfo.isActivationFailed || apexSessionInfo.isUnknown
-                || apexSessionInfo.isRolledBack || apexSessionInfo.isRollbackInProgress;
+                || apexSessionInfo.isRolledBack || apexSessionInfo.isRollbackInProgress
+                || apexSessionInfo.isRollbackFailed;
     }
 
     @GuardedBy("mStagedSessions")
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 5df2f86..35fa940 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -302,6 +302,7 @@
     }
 
     public void grantDefaultPermissions(int userId) {
+        removeSystemFixedStorage(userId);
         grantPermissionsToSysComponentsAndPrivApps(userId);
         grantDefaultSystemHandlerPermissions(userId);
         grantDefaultPermissionExceptions(userId);
@@ -310,6 +311,46 @@
         }
     }
 
+    // STOPSHIP: This is meant to fix the devices messed up by storage permission model 2 and
+    //           should be removed once all devices were updated
+    private void removeSystemFixedStorage(int userId) {
+        List<PackageInfo> packages = mContext.getPackageManager().getInstalledPackagesAsUser(
+                DEFAULT_PACKAGE_INFO_QUERY_FLAGS, userId);
+
+        for (PackageInfo pkg : packages) {
+            if (pkg == null || pkg.requestedPermissions == null) {
+                continue;
+            }
+
+            for (String permission : pkg.requestedPermissions) {
+                if (!(Manifest.permission.READ_EXTERNAL_STORAGE.equals(permission)
+                        || Manifest.permission.WRITE_EXTERNAL_STORAGE.equals(permission))) {
+                    continue;
+                }
+
+                int flags = mContext.getPackageManager().getPermissionFlags(permission,
+                        pkg.packageName, UserHandle.of(userId));
+                if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) == 0) {
+                    continue;
+                }
+
+                Log.v(TAG, "Removing system fixed " + pkg.packageName + "/" + permission);
+                mContext.getPackageManager().updatePermissionFlags(permission, pkg.packageName,
+                        PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, UserHandle.of(userId));
+
+                if (!doesPackageSupportRuntimePermissions(pkg)
+                        || (flags & (PackageManager.FLAG_PERMISSION_USER_SET
+                        | PackageManager.FLAG_PERMISSION_POLICY_FIXED)) != 0) {
+                    continue;
+                }
+
+                Log.v(TAG, "Revoking " + pkg.packageName + "/" + permission);
+                mContext.getPackageManager().revokeRuntimePermission(pkg.packageName, permission,
+                        UserHandle.of(userId));
+            }
+        }
+    }
+
     private void grantRuntimePermissionsForSystemPackage(int userId, PackageInfo pkg) {
         Set<String> permissions = new ArraySet<>();
         for (String permission : pkg.requestedPermissions) {
@@ -1322,6 +1363,9 @@
 
     private PackageInfo getPackageInfo(String pkg,
             @PackageManager.PackageInfoFlags int extraFlags) {
+        if (pkg == null) {
+            return null;
+        }
         try {
             return mContext.getPackageManager().getPackageInfo(pkg,
                     DEFAULT_PACKAGE_INFO_QUERY_FLAGS | extraFlags);
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 3011808..67f30dc 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -22,25 +22,28 @@
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.content.Context;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManagerInternal.PackageListObserver;
 import android.content.pm.PackageParser;
 import android.content.pm.PermissionInfo;
+import android.os.Process;
 import android.os.UserHandle;
-import android.os.UserManagerInternal;
 import android.permission.PermissionControllerManager;
 import android.permission.PermissionManagerInternal;
+import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
-import com.android.internal.util.function.QuadConsumer;
-import com.android.internal.util.function.TriConsumer;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 
 /**
@@ -51,11 +54,17 @@
  * and app ops - and vise versa.
  */
 public final class PermissionPolicyService extends SystemService {
+    private static final String PLATFORM_PACKAGE = "android";
 
     private static final String LOG_TAG = PermissionPolicyService.class.getSimpleName();
 
+    // No need to lock as this is populated on boot when the OS is
+    // single threaded and is never mutated until a reboot.
+    private static final ArraySet<String> sAllRestrictedPermissions = new ArraySet<>();
+
     public PermissionPolicyService(@NonNull Context context) {
         super(context);
+        cacheAllRestrictedPermissions(context);
     }
 
     @Override
@@ -89,6 +98,20 @@
         startWatchingRuntimePermissionChanges(getContext(), userId);
     }
 
+    private static void cacheAllRestrictedPermissions(@NonNull Context context) {
+        try {
+            final PackageInfo packageInfo = context.getPackageManager()
+                    .getPackageInfo(PLATFORM_PACKAGE, PackageManager.GET_PERMISSIONS);
+            for (PermissionInfo permissionInfo : packageInfo.permissions) {
+                if (permissionInfo.isRestricted()) {
+                    sAllRestrictedPermissions.add(permissionInfo.name);
+                }
+            }
+        } catch (NameNotFoundException impossible) {
+            /* cannot happen */
+        }
+    }
+
     private static void grantOrUpgradeDefaultRuntimePermissionsInNeeded(@NonNull Context context,
             @UserIdInt int userId) {
         final PackageManagerInternal packageManagerInternal = LocalServices.getService(
@@ -123,16 +146,6 @@
         }
     }
 
-    private static void onRestrictedPermissionEnabledChange(@NonNull Context context) {
-        final PermissionManagerInternal permissionManagerInternal = LocalServices
-                .getService(PermissionManagerInternal.class);
-        final UserManagerInternal userManagerInternal = LocalServices.getService(
-                UserManagerInternal.class);
-        for (int userId : userManagerInternal.getUserIds()) {
-            synchronizePermissionsAndAppOpsForUser(context, userId);
-        }
-    }
-
     private static void startWatchingRuntimePermissionChanges(@NonNull Context context,
             int userId) {
         final PermissionManagerInternal permissionManagerInternal = LocalServices.getService(
@@ -149,40 +162,66 @@
             @NonNull String packageName, @UserIdInt int userId) {
         final PackageManagerInternal packageManagerInternal = LocalServices.getService(
                 PackageManagerInternal.class);
-        final PackageParser.Package pkg = packageManagerInternal
-                .getPackage(packageName);
-        if (pkg != null) {
-            PermissionToOpSynchronizer.syncPackage(context, pkg, userId);
+        final PackageParser.Package pkg = packageManagerInternal.getPackage(packageName);
+        if (pkg == null) {
+            return;
         }
+        final PermissionToOpSynchroniser synchroniser = new PermissionToOpSynchroniser(context);
+        synchroniser.addPackage(context, pkg, userId);
+        final String[] sharedPkgNames = packageManagerInternal.getPackagesForSharedUserId(
+                pkg.mSharedUserId, userId);
+        if (sharedPkgNames != null) {
+            for (String sharedPkgName : sharedPkgNames) {
+                final PackageParser.Package sharedPkg = packageManagerInternal
+                        .getPackage(sharedPkgName);
+                if (sharedPkg != null) {
+                    synchroniser.addPackage(context, sharedPkg, userId);
+                }
+            }
+        }
+        synchroniser.syncPackages();
     }
 
     private static void synchronizePermissionsAndAppOpsForUser(@NonNull Context context,
             @UserIdInt int userId) {
         final PackageManagerInternal packageManagerInternal = LocalServices.getService(
                 PackageManagerInternal.class);
-        final PermissionToOpSynchronizer synchronizer = new PermissionToOpSynchronizer(context);
+        final PermissionToOpSynchroniser synchronizer = new PermissionToOpSynchroniser(context);
         packageManagerInternal.forEachPackage((pkg) ->
                 synchronizer.addPackage(context, pkg, userId));
         synchronizer.syncPackages();
     }
 
-    private static class PermissionToOpSynchronizer {
+    /**
+     * Synchronizes permission to app ops. You *must* always sync all packages
+     * in a shared UID at the same time to ensure proper synchronization.
+     */
+    private static class PermissionToOpSynchroniser {
         private final @NonNull Context mContext;
 
+        private final @NonNull SparseIntArray mUids = new SparseIntArray();
         private final @NonNull SparseArray<String> mPackageNames = new SparseArray<>();
         private final @NonNull SparseIntArray mAllowedUidOps = new SparseIntArray();
         private final @NonNull SparseIntArray mDefaultUidOps = new SparseIntArray();
 
-        PermissionToOpSynchronizer(@NonNull Context context) {
+        PermissionToOpSynchroniser(@NonNull Context context) {
             mContext = context;
         }
 
-        private void addPackage(@NonNull Context context,
-                @NonNull PackageParser.Package pkg, @UserIdInt int userId) {
-            addPackage(context, pkg, userId, this::addAllowedEntry, this::addIgnoredEntry);
-        }
-
         void syncPackages() {
+            // TRICKY: we set the app op for a restricted permission to allow if the app
+            // requesting the permission is whitelisted and to deny if the app requesting
+            // the permission is not whitelisted. However, there is another case where an
+            // app in a shared user can access a component in another app in the same shared
+            // user due to being in the same shared user and not by having the permission
+            // that guards the component form the rest of the world. We need to handle this.
+            // The way we do this is by setting app ops corresponding to non requested
+            // restricted permissions to allow as this would allow the shared uid access
+            // case and be okay for other apps as they would not have the permission and
+            // would fail on the permission checks before reaching the app op check.
+            final SparseArray<List<String>> unrequestedRestrictedPermissionsForUid =
+                    new SparseArray<>();
+
             final AppOpsManager appOpsManager = mContext.getSystemService(AppOpsManager.class);
             final int allowedCount = mAllowedUidOps.size();
             for (int i = 0; i < allowedCount; i++) {
@@ -190,31 +229,84 @@
                 final int uid = mAllowedUidOps.valueAt(i);
                 final String packageName = mPackageNames.valueAt(i);
                 setUidModeAllowed(appOpsManager, opCode, uid, packageName);
+
+                // Keep track this permission was requested by the UID.
+                List<String> unrequestedRestrictedPermissions =
+                        unrequestedRestrictedPermissionsForUid.get(uid);
+                if (unrequestedRestrictedPermissions == null) {
+                    unrequestedRestrictedPermissions = new ArrayList<>(sAllRestrictedPermissions);
+                    unrequestedRestrictedPermissionsForUid.put(uid,
+                            unrequestedRestrictedPermissions);
+                }
+                unrequestedRestrictedPermissions.remove(AppOpsManager.opToPermission(opCode));
+
+                mUids.delete(uid);
             }
             final int defaultCount = mDefaultUidOps.size();
             for (int i = 0; i < defaultCount; i++) {
                 final int opCode = mDefaultUidOps.keyAt(i);
                 final int uid = mDefaultUidOps.valueAt(i);
                 setUidModeDefault(appOpsManager, opCode, uid);
+
+                // Keep track this permission was requested by the UID.
+                List<String> unrequestedRestrictedPermissions =
+                        unrequestedRestrictedPermissionsForUid.get(uid);
+                if (unrequestedRestrictedPermissions == null) {
+                    unrequestedRestrictedPermissions = new ArrayList<>(sAllRestrictedPermissions);
+                    unrequestedRestrictedPermissionsForUid.put(uid,
+                            unrequestedRestrictedPermissions);
+                }
+                unrequestedRestrictedPermissions.remove(AppOpsManager.opToPermission(opCode));
+
+                mUids.delete(uid);
+            }
+
+            // Give root access
+            mUids.put(Process.ROOT_UID, Process.ROOT_UID);
+
+            // Add records for UIDs that don't use any restricted permissions.
+            final int uidCount = mUids.size();
+            for (int i = 0; i < uidCount; i++) {
+                final int uid = mUids.keyAt(i);
+                unrequestedRestrictedPermissionsForUid.put(uid,
+                        new ArrayList<>(sAllRestrictedPermissions));
+            }
+
+            // Flip ops for all unrequested restricted permission for the UIDs.
+            final int unrequestedUidCount = unrequestedRestrictedPermissionsForUid.size();
+            for (int i = 0; i < unrequestedUidCount; i++) {
+                final List<String> unrequestedRestrictedPermissions =
+                        unrequestedRestrictedPermissionsForUid.valueAt(i);
+                if (unrequestedRestrictedPermissions != null) {
+                    final int uid = unrequestedRestrictedPermissionsForUid.keyAt(i);
+                    final String[] packageNames = (uid != Process.ROOT_UID)
+                            ? mContext.getPackageManager().getPackagesForUid(uid)
+                            : new String[] {"root"};
+                    if (packageNames == null) {
+                        continue;
+                    }
+                    final int permissionCount = unrequestedRestrictedPermissions.size();
+                    for (int j = 0; j < permissionCount; j++) {
+                        final String permission = unrequestedRestrictedPermissions.get(j);
+                        for (String packageName : packageNames) {
+                            setUidModeAllowed(appOpsManager,
+                                    AppOpsManager.permissionToOpCode(permission), uid,
+                                    packageName);
+                        }
+                    }
+                }
             }
         }
 
-        static void syncPackage(@NonNull Context context, @NonNull PackageParser.Package pkg,
-                @UserIdInt int userId) {
-            addPackage(context, pkg, userId, PermissionToOpSynchronizer::setUidModeAllowed,
-                    PermissionToOpSynchronizer::setUidModeDefault);
-        }
-
-        private static void addPackage(@NonNull Context context,
-                @NonNull PackageParser.Package pkg, @UserIdInt int userId,
-                @NonNull QuadConsumer<AppOpsManager, Integer, Integer, String> allowedConsumer,
-                @NonNull TriConsumer<AppOpsManager, Integer, Integer> defaultConsumer) {
+        private void addPackage(@NonNull Context context,
+                @NonNull PackageParser.Package pkg, @UserIdInt int userId) {
             final PackageManager packageManager = context.getPackageManager();
-            final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
 
             final int uid = UserHandle.getUid(userId, UserHandle.getAppId(pkg.applicationInfo.uid));
             final UserHandle userHandle = UserHandle.of(userId);
 
+            mUids.put(uid, uid);
+
             final int permissionCount = pkg.requestedPermissions.size();
             for (int i = 0; i < permissionCount; i++) {
                 final String permission = pkg.requestedPermissions.get(i);
@@ -241,9 +333,10 @@
 
                 if (permissionInfo.isHardRestricted()) {
                     if (applyRestriction) {
-                        defaultConsumer.accept(appOpsManager, opCode, uid);
+                        mDefaultUidOps.put(opCode, uid);
                     } else {
-                        allowedConsumer.accept(appOpsManager, opCode, uid, pkg.packageName);
+                        mPackageNames.put(opCode, pkg.packageName);
+                        mAllowedUidOps.put(opCode, uid);
                     }
                 } else if (permissionInfo.isSoftRestricted()) {
                     //TODO: Implement soft restrictions like storage here.
@@ -251,19 +344,6 @@
             }
         }
 
-        @SuppressWarnings("unused")
-        private void addAllowedEntry(@NonNull AppOpsManager appOpsManager, int opCode,
-                int uid, @NonNull String packageName) {
-            mPackageNames.put(opCode, packageName);
-            mAllowedUidOps.put(opCode, uid);
-        }
-
-        @SuppressWarnings("unused")
-        private void addIgnoredEntry(@NonNull AppOpsManager appOpsManager,
-                int opCode, int uid) {
-            mDefaultUidOps.put(opCode, uid);
-        }
-
         private static void setUidModeAllowed(@NonNull AppOpsManager appOpsManager,
                 int opCode, int uid, @NonNull String packageName) {
             final int currentMode = appOpsManager.unsafeCheckOpRaw(AppOpsManager
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index e08e1ff..e2253e7 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -693,13 +693,13 @@
         }
     }
 
-    void scheduleTopResumedActivityChanged(boolean onTop) {
+    boolean scheduleTopResumedActivityChanged(boolean onTop) {
         if (!attachedToProcess()) {
             if (DEBUG_STATES) {
                 Slog.w(TAG, "Can't report activity position update - client not running"
                                 + ", activityRecord=" + this);
             }
-            return;
+            return false;
         }
         try {
             if (DEBUG_STATES) {
@@ -710,7 +710,9 @@
                     TopResumedActivityChangeItem.obtain(onTop));
         } catch (RemoteException e) {
             // If process died, whatever.
+            return false;
         }
+        return true;
     }
 
     void updateMultiWindowMode() {
@@ -3408,7 +3410,6 @@
             transaction.addCallback(callbackItem);
             transaction.setLifecycleStateRequest(lifecycleItem);
             mAtmService.getLifecycleManager().scheduleTransaction(transaction);
-            mStackSupervisor.updateTopResumedActivityIfNeeded();
             // Note: don't need to call pauseIfSleepingLocked() here, because the caller will only
             // request resume if this activity is currently resumed, which implies we aren't
             // sleeping.
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index fad4dbd..419f5be 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -1500,7 +1500,6 @@
                 + " callers=" + Debug.getCallers(5));
         r.setState(RESUMED, "minimalResumeActivityLocked");
         r.completeResumeLocked();
-        mStackSupervisor.updateTopResumedActivityIfNeeded();
         if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE,
                 "Launch completed; removing icicle of " + r.icicle);
     }
@@ -2571,7 +2570,6 @@
             // Protect against recursion.
             mInResumeTopActivity = true;
             result = resumeTopActivityInnerLocked(prev, options);
-            mStackSupervisor.updateTopResumedActivityIfNeeded();
 
             // When resuming the top activity, it may be necessary to pause the top activity (for
             // example, returning to the lock screen. We suppress the normal pause logic in
@@ -2606,6 +2604,7 @@
         if (DEBUG_STACK) Slog.d(TAG_STACK, "setResumedActivity stack:" + this + " + from: "
                 + mResumedActivity + " to:" + r + " reason:" + reason);
         mResumedActivity = r;
+        mStackSupervisor.updateTopResumedActivityIfNeeded();
     }
 
     @GuardedBy("mService")
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index 53dc1df..afdbd73 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -853,7 +853,6 @@
 
                 // Schedule transaction.
                 mService.getLifecycleManager().scheduleTransaction(clientTransaction);
-                updateTopResumedActivityIfNeeded();
 
                 if ((proc.mInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0
                         && mService.mHasHeavyWeightFeature) {
@@ -2321,8 +2320,8 @@
         // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
         // before the prevTopActivity one hasn't reported back yet. So server never sent the top
         // resumed state change message to prevTopActivity.
-        if (prevActivityReceivedTopState) {
-            prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */);
+        if (prevActivityReceivedTopState
+                && prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
             scheduleTopResumedStateLossTimeout(prevTopActivity);
             mTopResumedActivityWaitingForPrev = true;
         }
diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java
index 714c227..9713bef 100644
--- a/services/core/java/com/android/server/wm/TaskRecord.java
+++ b/services/core/java/com/android/server/wm/TaskRecord.java
@@ -1688,6 +1688,8 @@
             int colorBackground = 0;
             int statusBarColor = 0;
             int navigationBarColor = 0;
+            boolean statusBarContrastWhenTransparent = false;
+            boolean navigationBarContrastWhenTransparent = false;
             boolean topActivity = true;
             for (--activityNdx; activityNdx >= 0; --activityNdx) {
                 final ActivityRecord r = mActivities.get(activityNdx);
@@ -1711,12 +1713,17 @@
                         colorBackground = r.taskDescription.getBackgroundColor();
                         statusBarColor = r.taskDescription.getStatusBarColor();
                         navigationBarColor = r.taskDescription.getNavigationBarColor();
+                        statusBarContrastWhenTransparent =
+                                r.taskDescription.getEnsureStatusBarContrastWhenTransparent();
+                        navigationBarContrastWhenTransparent =
+                                r.taskDescription.getEnsureNavigationBarContrastWhenTransparent();
                     }
                 }
                 topActivity = false;
             }
             lastTaskDescription = new TaskDescription(label, null, iconResource, iconFilename,
-                    colorPrimary, colorBackground, statusBarColor, navigationBarColor);
+                    colorPrimary, colorBackground, statusBarColor, navigationBarColor,
+                    statusBarContrastWhenTransparent, navigationBarContrastWhenTransparent);
             if (mTask != null) {
                 mTask.setTaskDescription(lastTaskDescription);
             }
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 6fe8b43..f31416c 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -362,11 +362,9 @@
         }
         final int color = ColorUtils.setAlphaComponent(
                 task.getTaskDescription().getBackgroundColor(), 255);
-        final int statusBarColor = task.getTaskDescription().getStatusBarColor();
-        final int navigationBarColor = task.getTaskDescription().getNavigationBarColor();
         final LayoutParams attrs = mainWindow.getAttrs();
         final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
-                attrs.privateFlags, attrs.systemUiVisibility, statusBarColor, navigationBarColor);
+                attrs.privateFlags, attrs.systemUiVisibility, task.getTaskDescription());
         final int width = mainWindow.getFrameLw().width();
         final int height = mainWindow.getFrameLw().height();
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 39b5662..5d99db5 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -18,6 +18,8 @@
 
 import static android.graphics.Color.WHITE;
 import static android.graphics.Color.alpha;
+import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
@@ -148,9 +150,8 @@
         final Rect tmpStableInsets = new Rect();
         final InsetsState mTmpInsetsState = new InsetsState();
         final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
-        int backgroundColor = WHITE;
-        int statusBarColor = 0;
-        int navigationBarColor = 0;
+        final TaskDescription taskDescription = new TaskDescription();
+        taskDescription.setBackgroundColor(WHITE);
         final int sysUiVis;
         final int windowFlags;
         final int windowPrivateFlags;
@@ -194,11 +195,9 @@
             layoutParams.systemUiVisibility = sysUiVis;
             layoutParams.setTitle(String.format(TITLE_FORMAT, task.mTaskId));
 
-            final TaskDescription taskDescription = task.getTaskDescription();
-            if (taskDescription != null) {
-                backgroundColor = taskDescription.getBackgroundColor();
-                statusBarColor = taskDescription.getStatusBarColor();
-                navigationBarColor = taskDescription.getNavigationBarColor();
+            final TaskDescription td = task.getTaskDescription();
+            if (td != null) {
+                taskDescription.copyFrom(td);
             }
             taskBounds = new Rect();
             task.getBounds(taskBounds);
@@ -216,8 +215,8 @@
             // Local call.
         }
         final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
-                surfaceControl, snapshot, layoutParams.getTitle(), backgroundColor, statusBarColor,
-                navigationBarColor, sysUiVis, windowFlags, windowPrivateFlags, taskBounds,
+                surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, sysUiVis,
+                windowFlags, windowPrivateFlags, taskBounds,
                 currentOrientation);
         window.setOuter(snapshotSurface);
         try {
@@ -234,9 +233,9 @@
 
     @VisibleForTesting
     TaskSnapshotSurface(WindowManagerService service, Window window, SurfaceControl surfaceControl,
-            TaskSnapshot snapshot, CharSequence title, int backgroundColor, int statusBarColor,
-            int navigationBarColor, int sysUiVis, int windowFlags, int windowPrivateFlags,
-            Rect taskBounds, int currentOrientation) {
+            TaskSnapshot snapshot, CharSequence title, TaskDescription taskDescription,
+            int sysUiVis, int windowFlags, int windowPrivateFlags, Rect taskBounds,
+            int currentOrientation) {
         mService = service;
         mSurface = new Surface();
         mHandler = new Handler(mService.mH.getLooper());
@@ -245,11 +244,12 @@
         mSurfaceControl = surfaceControl;
         mSnapshot = snapshot;
         mTitle = title;
+        int backgroundColor = taskDescription.getBackgroundColor();
         mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
         mTaskBounds = taskBounds;
         mSystemBarBackgroundPainter = new SystemBarBackgroundPainter(windowFlags,
-                windowPrivateFlags, sysUiVis, statusBarColor, navigationBarColor);
-        mStatusBarColor = statusBarColor;
+                windowPrivateFlags, sysUiVis, taskDescription);
+        mStatusBarColor = taskDescription.getStatusBarColor();
         mOrientationOnCreation = currentOrientation;
     }
 
@@ -490,7 +490,7 @@
         private final int mSysUiVis;
 
         SystemBarBackgroundPainter( int windowFlags, int windowPrivateFlags, int sysUiVis,
-                int statusBarColor, int navigationBarColor) {
+                TaskDescription taskDescription) {
             mWindowFlags = windowFlags;
             mWindowPrivateFlags = windowPrivateFlags;
             mSysUiVis = sysUiVis;
@@ -498,11 +498,17 @@
             final int semiTransparent = context.getColor(
                     R.color.system_bar_background_semi_transparent);
             mStatusBarColor = DecorView.calculateBarColor(windowFlags, FLAG_TRANSLUCENT_STATUS,
-                    semiTransparent, statusBarColor);
+                    semiTransparent, taskDescription.getStatusBarColor(), sysUiVis,
+                    SYSTEM_UI_FLAG_LIGHT_STATUS_BAR,
+                    taskDescription.getEnsureStatusBarContrastWhenTransparent());
             mNavigationBarColor = DecorView.calculateBarColor(windowFlags,
-                    FLAG_TRANSLUCENT_NAVIGATION, semiTransparent, navigationBarColor);
+                    FLAG_TRANSLUCENT_NAVIGATION, semiTransparent,
+                    taskDescription.getNavigationBarColor(), sysUiVis,
+                    SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR,
+                    taskDescription.getEnsureNavigationBarContrastWhenTransparent()
+                            && context.getResources().getBoolean(R.bool.config_navBarNeedsScrim));
             mStatusBarPaint.setColor(mStatusBarColor);
-            mNavigationBarPaint.setColor(navigationBarColor);
+            mNavigationBarPaint.setColor(mNavigationBarColor);
         }
 
         void setInsets(Rect contentInsets, Rect stableInsets) {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 8f2a2d2..8f1709e 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4096,6 +4096,9 @@
 
     @Override
     public boolean isSeparateProfileChallengeAllowed(int userHandle) {
+        if (!isCallerWithSystemUid()) {
+            throw new SecurityException("Caller must be system");
+        }
         ComponentName profileOwner = getProfileOwner(userHandle);
         // Profile challenge is supported on N or newer release.
         return profileOwner != null &&
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 43fe674..8aaf29a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -23,9 +23,11 @@
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertTrue;
 
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
@@ -36,6 +38,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -61,6 +64,7 @@
 
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.invocation.InvocationOnMock;
@@ -965,6 +969,62 @@
     }
 
     @Test
+    public void testOnNullBinding() throws Exception {
+        Context context = mock(Context.class);
+        PackageManager pm = mock(PackageManager.class);
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+        when(context.getPackageName()).thenReturn(mContext.getPackageName());
+        when(context.getUserId()).thenReturn(mContext.getUserId());
+        when(context.getPackageManager()).thenReturn(pm);
+        when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+                APPROVAL_BY_COMPONENT);
+        ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+        service.registerSystemService(cn, 0);
+        when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            ServiceConnection sc = (ServiceConnection) args[1];
+            sc.onNullBinding(cn);
+            return true;
+        });
+
+        service.registerSystemService(cn, 0);
+        assertFalse(service.isBound(cn, 0));
+    }
+
+    @Test
+    public void testOnServiceConnected() throws Exception {
+        Context context = mock(Context.class);
+        PackageManager pm = mock(PackageManager.class);
+        ApplicationInfo ai = new ApplicationInfo();
+        ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+        when(context.getPackageName()).thenReturn(mContext.getPackageName());
+        when(context.getUserId()).thenReturn(mContext.getUserId());
+        when(context.getPackageManager()).thenReturn(pm);
+        when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+        ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+                APPROVAL_BY_COMPONENT);
+        ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+        service.registerSystemService(cn, 0);
+        when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+            Object[] args = invocation.getArguments();
+            ServiceConnection sc = (ServiceConnection) args[1];
+            sc.onServiceConnected(cn, mock(IBinder.class));
+            return true;
+        });
+
+        service.registerSystemService(cn, 0);
+        assertTrue(service.isBound(cn, 0));
+    }
+
+    @Test
     public void testOnPackagesChanged_nullValuesPassed_noNullPointers() {
         for (int approvalLevel : new int[] {APPROVAL_BY_COMPONENT, APPROVAL_BY_PACKAGE}) {
             ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index b078825..355ff63 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -348,6 +348,7 @@
         when(mPackageManagerClient.hasSystemFeature(FEATURE_WATCH)).thenReturn(false);
         when(mUgmInternal.newUriPermissionOwner(anyString())).thenReturn(mPermOwner);
         when(mPackageManager.getPackagesForUid(mUid)).thenReturn(new String[]{PKG});
+        when(mPackageManagerClient.getPackagesForUid(anyInt())).thenReturn(new String[]{PKG});
 
         // write to a test file; the system file isn't readable from tests
         mFile = new File(mContext.getCacheDir(), "test.xml");
@@ -4289,6 +4290,36 @@
     }
 
     @Test
+    public void testFlagBubble() throws RemoteException {
+        // Bubbles are allowed!
+        mService.setPreferencesHelper(mPreferencesHelper);
+        when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(true);
+        when(mPreferencesHelper.getNotificationChannel(
+                anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
+                mTestNotificationChannel);
+        when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
+                mTestNotificationChannel.getImportance());
+
+        // Notif with bubble metadata but not our other misc requirements
+        NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
+                null /* tvExtender */, true /* isBubble */);
+
+        // Say we're foreground
+        when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
+                IMPORTANCE_FOREGROUND);
+
+        mBinderService.enqueueNotificationWithTag(PKG, PKG, "tag",
+                nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+        waitForIdle();
+
+        StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
+        assertEquals(1, notifs.length);
+        assertTrue((notifs[0].getNotification().flags & FLAG_BUBBLE) != 0);
+        assertTrue(mService.getNotificationRecord(
+                nr.sbn.getKey()).getNotification().isBubbleNotification());
+    }
+
+    @Test
     public void testFlagBubbleNotifs_flag_appForeground() throws RemoteException {
         // Bubbles are allowed!
         mService.setPreferencesHelper(mPreferencesHelper);
@@ -4918,22 +4949,26 @@
         assertEquals(1, mService.getNotificationRecordCount());
     }
 
-    public void testGetAllowedAssistantCapabilities() throws Exception {
-        List<String> capabilities = mBinderService.getAllowedAssistantCapabilities(null);
+    @Test
+    public void testGetAllowedAssistantAdjustments() throws Exception {
+        List<String> capabilities = mBinderService.getAllowedAssistantAdjustments(null);
         assertNotNull(capabilities);
 
         for (int i = capabilities.size() - 1; i >= 0; i--) {
             String capability = capabilities.get(i);
-            mBinderService.disallowAssistantCapability(capability);
-            assertEquals(i + 1, mBinderService.getAllowedAssistantCapabilities(null).size());
-            List<String> currentCapabilities = mBinderService.getAllowedAssistantCapabilities(null);
+            mBinderService.disallowAssistantAdjustment(capability);
+            assertEquals(i + 1, mBinderService.getAllowedAssistantAdjustments(null).size());
+            List<String> currentCapabilities = mBinderService.getAllowedAssistantAdjustments(null);
             assertNotNull(currentCapabilities);
             assertFalse(currentCapabilities.contains(capability));
         }
     }
 
+    @Test
     public void testAdjustRestrictedKey() throws Exception {
         NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);
+        mService.addNotification(r);
+        when(mAssistants.isSameUser(any(), anyInt())).thenReturn(true);
 
         when(mAssistants.isAdjustmentAllowed(KEY_IMPORTANCE)).thenReturn(true);
         when(mAssistants.isAdjustmentAllowed(KEY_USER_SENTIMENT)).thenReturn(false);
@@ -4951,8 +4986,12 @@
         assertEquals(USER_SENTIMENT_NEUTRAL, r.getUserSentiment());
     }
 
+    @Test
     public void testAutomaticZenRuleValidation_policyFilterAgreement() throws Exception {
-        ComponentName owner = mock(ComponentName.class);
+        when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt()))
+                .thenReturn(true);
+        mService.setZenHelper(mock(ZenModeHelper.class));
+        ComponentName owner = new ComponentName(mContext, this.getClass());
         ZenPolicy zenPolicy = new ZenPolicy.Builder().allowAlarms(true).build();
         boolean isEnabled = true;
         AutomaticZenRule rule = new AutomaticZenRule("test", owner, owner, mock(Uri.class),
@@ -4960,7 +4999,7 @@
 
         try {
             mBinderService.addAutomaticZenRule(rule);
-            fail("Zen policy only aplies to priority only mode");
+            fail("Zen policy only applies to priority only mode");
         } catch (IllegalArgumentException e) {
             // yay
         }
@@ -4974,6 +5013,7 @@
         mBinderService.addAutomaticZenRule(rule);
     }
 
+    @Test
     public void testAreNotificationsEnabledForPackage_crossUser() throws Exception {
         try {
             mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(),
@@ -4990,6 +5030,7 @@
                 mUid + UserHandle.PER_USER_RANGE);
     }
 
+    @Test
     public void testAreBubblesAllowedForPackage_crossUser() throws Exception {
         try {
             mBinderService.areBubblesAllowedForPackage(mContext.getPackageName(),
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index ca815ec..cb6dc6d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -30,6 +30,7 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.eq;
 
+import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.ComponentName;
 import android.graphics.Canvas;
@@ -38,7 +39,6 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
-import android.view.Surface;
 import android.view.SurfaceControl;
 
 import androidx.test.filters.SmallTest;
@@ -67,8 +67,17 @@
                 ORIENTATION_PORTRAIT, contentInsets, false, 1.0f, true /* isRealSnapshot */,
                 WINDOWING_MODE_FULLSCREEN, 0 /* systemUiVisibility */, false /* isTranslucent */);
         mSurface = new TaskSnapshotSurface(mWm, new Window(), new SurfaceControl(), snapshot, "Test",
-                Color.WHITE, Color.RED, Color.BLUE, sysuiVis, windowFlags, 0, taskBounds,
-                ORIENTATION_PORTRAIT);
+                createTaskDescription(Color.WHITE, Color.RED, Color.BLUE), sysuiVis, windowFlags, 0,
+                taskBounds, ORIENTATION_PORTRAIT);
+    }
+
+    private static TaskDescription createTaskDescription(int background, int statusBar,
+            int navigationBar) {
+        final TaskDescription td = new TaskDescription();
+        td.setBackgroundColor(background);
+        td.setStatusBarColor(statusBar);
+        td.setNavigationBarColor(navigationBar);
+        return td;
     }
 
     private void setupSurface(int width, int height) {
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 52e0ebd..2d8a8cb 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -4321,6 +4321,22 @@
          * @hide
          */
         public static final String IS_USING_CARRIER_AGGREGATION = "is_using_carrier_aggregation";
+
+        /**
+         * The current registered raw data network operator name in long alphanumeric format.
+         * <p>
+         * This is the same as {@link ServiceState#getOperatorAlphaLongRaw()}.
+         * @hide
+         */
+        public static final String OPERATOR_ALPHA_LONG_RAW = "operator_alpha_long_raw";
+
+        /**
+         * The current registered raw data network operator name in short alphanumeric format.
+         * <p>
+         * This is the same as {@link ServiceState#getOperatorAlphaShortRaw()}.
+         * @hide
+         */
+        public static final String OPERATOR_ALPHA_SHORT_RAW = "operator_alpha_short_raw";
     }
 
     /**
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 970bbf8..9f6528b 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1312,6 +1312,24 @@
             "hide_lte_plus_data_icon_bool";
 
     /**
+     * The string is used to filter redundant string from PLMN Network Name that's supplied by
+     * specific carrier.
+     *
+     * @hide
+     */
+    public static final String KEY_OPERATOR_NAME_FILTER_PATTERN_STRING =
+            "operator_name_filter_pattern_string";
+
+    /**
+     * The string is used to compare with operator name. If it matches the pattern then show
+     * specific data icon.
+     *
+     * @hide
+     */
+    public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING =
+            "show_carrier_data_icon_pattern_string";
+
+    /**
      * Boolean to decide whether to show precise call failed cause to user
      * @hide
      */
@@ -3152,6 +3170,8 @@
         sDefaults.putBoolean(KEY_SPN_DISPLAY_RULE_USE_ROAMING_FROM_SERVICE_STATE_BOOL, false);
         sDefaults.putBoolean(KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL, false);
         sDefaults.putBoolean(KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL, false);
+        sDefaults.putString(KEY_OPERATOR_NAME_FILTER_PATTERN_STRING, "");
+        sDefaults.putString(KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING, "");
         sDefaults.putBoolean(KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL, true);
         sDefaults.putBoolean(KEY_LTE_ENABLED_BOOL, true);
         sDefaults.putBoolean(KEY_SUPPORT_TDSCDMA_BOOL, false);
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index 3745277..3087516 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -49,10 +49,10 @@
 
     // long alpha Operator Name String or Enhanced Operator Name String
     /** @hide */
-    protected final String mAlphaLong;
+    protected String mAlphaLong;
     // short alpha Operator Name String or Enhanced Operator Name String
     /** @hide */
-    protected final String mAlphaShort;
+    protected String mAlphaShort;
 
     /** @hide */
     protected CellIdentity(String tag, int type, String mcc, String mnc, String alphal,
@@ -145,6 +145,13 @@
     }
 
     /**
+     * @hide
+     */
+    public void setOperatorAlphaLong(String alphaLong) {
+        mAlphaLong = alphaLong;
+    }
+
+    /**
      * @return The short alpha tag associated with the current scan result (may be the operator
      * name string or extended operator name string).  May be null if unknown.
      */
@@ -154,6 +161,13 @@
     }
 
     /**
+     * @hide
+     */
+    public void setOperatorAlphaShort(String alphaShort) {
+        mAlphaShort = alphaShort;
+    }
+
+    /**
      * @return a CellLocation object for this CellIdentity
      * @hide
      */
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 8c92e84..1a160f4 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -339,6 +339,9 @@
 
     private List<NetworkRegistrationInfo> mNetworkRegistrationInfos = new ArrayList<>();
 
+    private String mOperatorAlphaLongRaw;
+    private String mOperatorAlphaShortRaw;
+
     /**
      * get String description of roaming type
      * @hide
@@ -420,6 +423,8 @@
         mNetworkRegistrationInfos = s.mNetworkRegistrationInfos == null ? null :
                 new ArrayList<>(s.mNetworkRegistrationInfos);
         mNrFrequencyRange = s.mNrFrequencyRange;
+        mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw;
+        mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw;
     }
 
     /**
@@ -453,6 +458,8 @@
         mChannelNumber = in.readInt();
         mCellBandwidths = in.createIntArray();
         mNrFrequencyRange = in.readInt();
+        mOperatorAlphaLongRaw = in.readString();
+        mOperatorAlphaShortRaw = in.readString();
     }
 
     public void writeToParcel(Parcel out, int flags) {
@@ -478,6 +485,8 @@
         out.writeInt(mChannelNumber);
         out.writeIntArray(mCellBandwidths);
         out.writeInt(mNrFrequencyRange);
+        out.writeString(mOperatorAlphaLongRaw);
+        out.writeString(mOperatorAlphaShortRaw);
     }
 
     public int describeContents() {
@@ -836,7 +845,9 @@
                 mIsEmergencyOnly,
                 mLteEarfcnRsrpBoost,
                 mNetworkRegistrationInfos,
-                mNrFrequencyRange);
+                mNrFrequencyRange,
+                mOperatorAlphaLongRaw,
+                mOperatorAlphaShortRaw);
     }
 
     @Override
@@ -862,6 +873,8 @@
                 && equalsHandlesNulls(mCdmaDefaultRoamingIndicator,
                         s.mCdmaDefaultRoamingIndicator)
                 && mIsEmergencyOnly == s.mIsEmergencyOnly
+                && equalsHandlesNulls(mOperatorAlphaLongRaw, s.mOperatorAlphaLongRaw)
+                && equalsHandlesNulls(mOperatorAlphaShortRaw, s.mOperatorAlphaShortRaw)
                 && (mNetworkRegistrationInfos == null
                 ? s.mNetworkRegistrationInfos == null : s.mNetworkRegistrationInfos != null
                 && mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos))
@@ -1019,6 +1032,8 @@
             .append(", mLteEarfcnRsrpBoost=").append(mLteEarfcnRsrpBoost)
             .append(", mNetworkRegistrationInfos=").append(mNetworkRegistrationInfos)
             .append(", mNrFrequencyRange=").append(mNrFrequencyRange)
+            .append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
+            .append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
             .append("}").toString();
     }
 
@@ -1056,6 +1071,8 @@
                 .setTransportType(AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
                 .setRegistrationState(NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN)
                 .build());
+        mOperatorAlphaLongRaw = null;
+        mOperatorAlphaShortRaw = null;
     }
 
     public void setStateOutOfService() {
@@ -1297,6 +1314,8 @@
         m.putInt("ChannelNumber", mChannelNumber);
         m.putIntArray("CellBandwidths", mCellBandwidths);
         m.putInt("mNrFrequencyRange", mNrFrequencyRange);
+        m.putString("operator-alpha-long-raw", mOperatorAlphaLongRaw);
+        m.putString("operator-alpha-short-raw", mOperatorAlphaShortRaw);
     }
 
     /** @hide */
@@ -1906,4 +1925,36 @@
 
         return state;
     }
+
+    /**
+     * @hide
+     */
+    public void setOperatorAlphaLongRaw(String operatorAlphaLong) {
+        mOperatorAlphaLongRaw = operatorAlphaLong;
+    }
+
+    /**
+     * The current registered raw data network operator name in long alphanumeric format.
+     *
+     * @hide
+     */
+    public String getOperatorAlphaLongRaw() {
+        return mOperatorAlphaLongRaw;
+    }
+
+    /**
+     * @hide
+     */
+    public void setOperatorAlphaShortRaw(String operatorAlphaShort) {
+        mOperatorAlphaShortRaw = operatorAlphaShort;
+    }
+
+    /**
+     * The current registered raw data network operator name in short alphanumeric format.
+     *
+     * @hide
+     */
+    public String getOperatorAlphaShortRaw() {
+        return mOperatorAlphaShortRaw;
+    }
 }
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index ee28ca2..cf15b92 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -87,8 +87,8 @@
     private int mCarrierId;
 
     /**
-     * The source of the name, NAME_SOURCE_UNDEFINED, NAME_SOURCE_DEFAULT_SOURCE,
-     * NAME_SOURCE_SIM_SOURCE or NAME_SOURCE_USER_INPUT.
+     * The source of the name, NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SOURCE or
+     * NAME_SOURCE_USER_INPUT.
      */
     private int mNameSource;
 
@@ -103,7 +103,7 @@
     private String mNumber;
 
     /**
-     * Data roaming state, DATA_RAOMING_ENABLE, DATA_RAOMING_DISABLE
+     * Data roaming state, DATA_ROAMING_ENABLE, DATA_ROAMING_DISABLE
      */
     private int mDataRoaming;
 
@@ -306,8 +306,8 @@
     }
 
     /**
-     * @return the source of the name, eg NAME_SOURCE_UNDEFINED, NAME_SOURCE_DEFAULT_SOURCE,
-     * NAME_SOURCE_SIM_SOURCE or NAME_SOURCE_USER_INPUT.
+     * @return the source of the name, eg NAME_SOURCE_DEFAULT_SOURCE, NAME_SOURCE_SIM_SOURCE or
+     * NAME_SOURCE_USER_INPUT.
      * @hide
      */
     @UnsupportedAppUsage
@@ -316,8 +316,8 @@
     }
 
     /**
-     * Creates and returns an icon {@code Bitmap} to represent this {@code SubscriptionInfo} in a user
-     * interface.
+     * Creates and returns an icon {@code Bitmap} to represent this {@code SubscriptionInfo} in a
+     * user interface.
      *
      * @param context A {@code Context} to get the {@code DisplayMetrics}s from.
      *
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 57c84a6..0c63411 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -364,12 +364,6 @@
     public static final String NAME_SOURCE = "name_source";
 
     /**
-     * The name_source is undefined
-     * @hide
-     */
-    public static final int NAME_SOURCE_UNDEFINDED = -1;
-
-    /**
      * The name_source is the default
      * @hide
      */
@@ -1598,27 +1592,16 @@
     }
 
     /**
-     * Set display name by simInfo index
-     * @param displayName the display name of SIM card
-     * @param subId the unique SubscriptionInfo index in database
-     * @return the number of records updated
-     * @hide
-     */
-    public int setDisplayName(String displayName, int subId) {
-        return setDisplayName(displayName, subId, NAME_SOURCE_UNDEFINDED);
-    }
-
-    /**
      * Set display name by simInfo index with name source
      * @param displayName the display name of SIM card
      * @param subId the unique SubscriptionInfo index in database
      * @param nameSource 0: NAME_SOURCE_DEFAULT_SOURCE, 1: NAME_SOURCE_SIM_SOURCE,
-     *                   2: NAME_SOURCE_USER_INPUT, -1 NAME_SOURCE_UNDEFINED
+     *                   2: NAME_SOURCE_USER_INPUT
      * @return the number of records updated or < 0 if invalid subId
      * @hide
      */
     @UnsupportedAppUsage
-    public int setDisplayName(String displayName, int subId, long nameSource) {
+    public int setDisplayName(String displayName, int subId, int nameSource) {
         if (VDBG) {
             logd("[setDisplayName]+  displayName:" + displayName + " subId:" + subId
                     + " nameSource:" + nameSource);
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9c63a82..0d3bc1d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3790,6 +3790,7 @@
      * @hide
      * nobody seems to call this.
      */
+    @TestApi
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public String getLine1AlphaTag() {
         return getLine1AlphaTag(getSubId());
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 01fdae8..cfba052 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -145,21 +145,13 @@
     int setIconTint(int tint, int subId);
 
     /**
-     * Set display name by simInfo index
-     * @param displayName the display name of SIM card
-     * @param subId the unique SubscriptionInfo index in database
-     * @return the number of records updated
-     */
-    int setDisplayName(String displayName, int subId);
-
-    /**
      * Set display name by simInfo index with name source
      * @param displayName the display name of SIM card
      * @param subId the unique SubscriptionInfo index in database
      * @param nameSource, 0: DEFAULT_SOURCE, 1: SIM_SOURCE, 2: USER_INPUT
      * @return the number of records updated
      */
-    int setDisplayNameUsingSrc(String displayName, int subId, long nameSource);
+    int setDisplayNameUsingSrc(String displayName, int subId, int nameSource);
 
     /**
      * Set phone number by subId
diff --git a/tests/ProtoInputStreamTests/Android.mk b/tests/ProtoInputStreamTests/Android.mk
new file mode 100644
index 0000000..eb747cc
--- /dev/null
+++ b/tests/ProtoInputStreamTests/Android.mk
@@ -0,0 +1,34 @@
+# Copyright (C) 2019 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_PACKAGE_NAME := ProtoInputStreamTests
+LOCAL_PROTOC_OPTIMIZE_TYPE := nano
+LOCAL_MODULE_TAGS := tests optional
+LOCAL_SRC_FILES := \
+    $(call all-java-files-under, src) \
+    $(call all-proto-files-under, src)
+LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_CERTIFICATE := platform
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    androidx.test.rules \
+    frameworks-base-testutils \
+    mockito-target-minus-junit4
+
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/tests/ProtoInputStreamTests/AndroidManifest.xml b/tests/ProtoInputStreamTests/AndroidManifest.xml
new file mode 100644
index 0000000..c11aa73
--- /dev/null
+++ b/tests/ProtoInputStreamTests/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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.test.protoinputstream">
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.test.protoinputstream"
+                     android:label="ProtoInputStream Tests">
+    </instrumentation>
+</manifest>
\ No newline at end of file
diff --git a/tests/ProtoInputStreamTests/AndroidTest.xml b/tests/ProtoInputStreamTests/AndroidTest.xml
new file mode 100644
index 0000000..51ab88e
--- /dev/null
+++ b/tests/ProtoInputStreamTests/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Configuration for ProtoInputStream Tests">
+    <option name="test-suite-tag" value="ProtoInputStreamTests" />
+    <option name="config-descriptor:metadata" key="component" value="metrics" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="ProtoInputStreamTests.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.test.protoinputstream" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/tests/ProtoInputStreamTests/TEST_MAPPING b/tests/ProtoInputStreamTests/TEST_MAPPING
new file mode 100644
index 0000000..cf9f077
--- /dev/null
+++ b/tests/ProtoInputStreamTests/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "ProtoInputStreamTests"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java
new file mode 100644
index 0000000..c21c403
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBoolTest.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamBoolTest extends TestCase {
+
+    /**
+     * Test reading single bool field
+     */
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    /**
+     * Implementation of testRead with a given chunkSize.
+     */
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 3 -> 1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        boolean[] results = new boolean[2];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readBoolean(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(false, results[0]);
+        assertEquals(true, results[1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(false);
+        testReadCompat(true);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(boolean val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL;
+        final long fieldId = fieldFlags | ((long) 130 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.boolField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        boolean result = false; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readBoolean(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.boolField, result);
+    }
+
+
+    /**
+     * Test reading repeated bool field
+     */
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    /**
+     * Implementation of testRepeated with a given chunkSize.
+     */
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BOOL;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+
+                // 4 -> 0
+                (byte) 0x20,
+                (byte) 0x00,
+                // 4 -> 1
+                (byte) 0x20,
+                (byte) 0x01,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+
+                // 3 -> 0
+                (byte) 0x18,
+                (byte) 0x00,
+                // 3 -> 1
+                (byte) 0x18,
+                (byte) 0x01,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        boolean[][] results = new boolean[3][2];
+        int[] indices = new int[3];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readBoolean(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readBoolean(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readBoolean(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(false, results[0][0]);
+        assertEquals(false, results[0][1]);
+        assertEquals(true, results[1][0]);
+        assertEquals(true, results[1][1]);
+        assertEquals(false, results[2][0]);
+        assertEquals(true, results[2][1]);
+    }
+
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new boolean[0]);
+        testRepeatedCompat(new boolean[]{false, true});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(boolean[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BOOL;
+        final long fieldId = fieldFlags | ((long) 131 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.boolFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        boolean[] result = new boolean[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readBoolean(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.boolFieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.boolFieldRepeated[i], result[i]);
+        }
+    }
+
+    /**
+     * Test reading packed bool field
+     */
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    /**
+     * Implementation of testPacked with a given chunkSize.
+     */
+    public void testPacked(int chunkSize) throws IOException {
+
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_BOOL;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 4 -> 0,1
+                (byte) 0x22,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x01,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 3 -> 0,1
+                (byte) 0x1a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x01,
+        };
+
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        boolean[][] results = new boolean[3][2];
+        int[] indices = new int[3];
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readBoolean(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readBoolean(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readBoolean(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(false, results[0][0]);
+        assertEquals(false, results[0][1]);
+        assertEquals(true, results[1][0]);
+        assertEquals(true, results[1][1]);
+        assertEquals(false, results[2][0]);
+        assertEquals(true, results[2][1]);
+    }
+
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new boolean[0]);
+        testPackedCompat(new boolean[]{false, true});
+    }
+
+    /**
+     * Implementation of testPackedCompat with a given value.
+     */
+    private void testPackedCompat(boolean[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_BOOL;
+        final long fieldId = fieldFlags | ((long) 132 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.boolFieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        boolean[] result = new boolean[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readBoolean(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.boolFieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.boolFieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readInt(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BOOL;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readBoolean(fieldId1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) fieldId2:
+                        pi.readBoolean(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readBoolean(fieldId3);
+                        // don't fail, length delimited is ok (represents packed booleans)
+                        break;
+                    case (int) fieldId6:
+                        pi.readBoolean(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java
new file mode 100644
index 0000000..09fe40e
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamBytesTest.java
@@ -0,0 +1,423 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+public class ProtoInputStreamBytesTest extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> null - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x00,
+                // 2 -> { } - default value, written when repeated
+                (byte) 0x12,
+                (byte) 0x00,
+                // 5 -> { 0, 1, 2, 3, 4 }
+                (byte) 0x2a,
+                (byte) 0x05,
+                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
+                // 3 -> { 0, 1, 2, 3, 4, 5 }
+                (byte) 0x1a,
+                (byte) 0x06,
+                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05,
+                // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc }
+                (byte) 0x22,
+                (byte) 0x04,
+                (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        byte[][] results = new byte[4][];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0] = pi.readBytes(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readBytes(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readBytes(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readBytes(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertTrue("Expected: []  Actual: " + Arrays.toString(results[0]),
+                Arrays.equals(new byte[]{}, results[0]));
+        assertTrue("Expected: []  Actual: " + Arrays.toString(results[1]),
+                Arrays.equals(new byte[]{}, results[1]));
+        assertTrue("Expected: [0, 1, 2, 3, 4, 5]  Actual: " + Arrays.toString(results[2]),
+                Arrays.equals(new byte[]{0, 1, 2, 3, 4, 5}, results[2]));
+        assertTrue("Expected: [-1, -2, -3, -4]  Actual: " + Arrays.toString(results[3]),
+                Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc},
+                        results[3]));
+    }
+
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(new byte[0]);
+        testReadCompat(new byte[]{1, 2, 3, 4});
+        testReadCompat(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc});
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(byte[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
+        final long fieldId = fieldFlags | ((long) 150 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.bytesField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        byte[] result = new byte[val.length];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readBytes(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.bytesField.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.bytesField[i], result[i]);
+        }
+    }
+
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BYTES;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> null - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x00,
+                // 2 -> { } - default value, written when repeated
+                (byte) 0x12,
+                (byte) 0x00,
+                // 3 -> { 0, 1, 2, 3, 4, 5 }
+                (byte) 0x1a,
+                (byte) 0x06,
+                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05,
+                // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc }
+                (byte) 0x22,
+                (byte) 0x04,
+                (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc,
+
+                // 5 -> { 0, 1, 2, 3, 4}
+                (byte) 0x2a,
+                (byte) 0x05,
+                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
+
+                // 1 -> null - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x00,
+                // 2 -> { } - default value, written when repeated
+                (byte) 0x12,
+                (byte) 0x00,
+                // 3 -> { 0, 1, 2, 3, 4, 5 }
+                (byte) 0x1a,
+                (byte) 0x06,
+                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, (byte) 0x05,
+                // 4 -> { (byte)0xff, (byte)0xfe, (byte)0xfd, (byte)0xfc }
+                (byte) 0x22,
+                (byte) 0x04,
+                (byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        byte[][][] results = new byte[4][2][];
+        int[] indices = new int[4];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readBytes(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readBytes(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readBytes(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readBytes(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assert (Arrays.equals(new byte[]{}, results[0][0]));
+        assert (Arrays.equals(new byte[]{}, results[0][1]));
+        assert (Arrays.equals(new byte[]{}, results[1][0]));
+        assert (Arrays.equals(new byte[]{}, results[1][1]));
+        assert (Arrays.equals(new byte[]{1, 2, 3, 4}, results[2][0]));
+        assert (Arrays.equals(new byte[]{1, 2, 3, 4}, results[2][1]));
+        assert (Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc},
+                results[3][0]));
+        assert (Arrays.equals(new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc},
+                results[3][1]));
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new byte[0][]);
+        testRepeatedCompat(new byte[][]{
+                new byte[0],
+                new byte[]{1, 2, 3, 4},
+                new byte[]{(byte) 0xff, (byte) 0xfe, (byte) 0xfd, (byte) 0xfc}
+        });
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(byte[][] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_BYTES;
+        final long fieldId = fieldFlags | ((long) 151 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.bytesFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        byte[][] result = new byte[val.length][]; // start off with default value
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readBytes(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.bytesFieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.bytesFieldRepeated[i].length, result[i].length);
+            for (int j = 0; j < result[i].length; j++) {
+                assertEquals(readback.bytesFieldRepeated[i][j], result[i][j]);
+            }
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> {1}
+                (byte) 0x0a,
+                (byte) 0x01,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readInt(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_BYTES;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readBytes(fieldId1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId2:
+                        pi.readBytes(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readBytes(fieldId3);
+                        // don't fail, length delimited is ok
+                        break;
+                    case (int) fieldId6:
+                        pi.readBytes(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java
new file mode 100644
index 0000000..118fe34
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamDoubleTest.java
@@ -0,0 +1,728 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamDoubleTest extends TestCase {
+
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 10 -> 1
+                (byte) 0x51,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x19,
+                (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e,
+                (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0,
+                // 4 -> 42.42
+                (byte) 0x21,
+                (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f,
+                (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40,
+                // 5 -> Double.MIN_NORMAL
+                (byte) 0x29,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Double.NEGATIVE_INFINITY
+                (byte) 0x39,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff,
+                // 8 -> Double.NaN
+                (byte) 0x41,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f,
+                // 9 -> Double.POSITIVE_INFINITY
+                (byte) 0x49,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        double[] results = new double[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readDouble(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readDouble(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readDouble(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readDouble(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5] = pi.readDouble(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6] = pi.readDouble(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    results[7] = pi.readDouble(fieldId8);
+                    break;
+                case (int) fieldId9:
+                    results[8] = pi.readDouble(fieldId9);
+                    break;
+                case (int) fieldId10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0, results[0]);
+        assertEquals(1.0, results[1]);
+        assertEquals(-1234.432, results[2]);
+        assertEquals(42.42, results[3]);
+        assertEquals(Double.MIN_NORMAL, results[4]);
+        assertEquals(Double.MIN_VALUE, results[5]);
+        assertEquals(Double.NEGATIVE_INFINITY, results[6]);
+        assertEquals(Double.NaN, results[7]);
+        assertEquals(Double.POSITIVE_INFINITY, results[8]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1234.432);
+        testReadCompat(42.42);
+        testReadCompat(Double.MIN_NORMAL);
+        testReadCompat(Double.MIN_VALUE);
+        testReadCompat(Double.NEGATIVE_INFINITY);
+        testReadCompat(Double.NaN);
+        testReadCompat(Double.POSITIVE_INFINITY);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(double val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE;
+        final long fieldId = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.doubleField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        double result = 0.0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readDouble(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.doubleField, result);
+    }
+
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_DOUBLE;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x19,
+                (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e,
+                (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0,
+                // 4 -> 42.42
+                (byte) 0x21,
+                (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f,
+                (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40,
+                // 5 -> Double.MIN_NORMAL
+                (byte) 0x29,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Double.NEGATIVE_INFINITY
+                (byte) 0x39,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff,
+                // 8 -> Double.NaN
+                (byte) 0x41,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f,
+                // 9 -> Double.POSITIVE_INFINITY
+                (byte) 0x49,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f,
+                // 10 -> 1
+                (byte) 0x51,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x19,
+                (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e,
+                (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0,
+                // 4 -> 42.42
+                (byte) 0x21,
+                (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f,
+                (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40,
+                // 5 -> Double.MIN_NORMAL
+                (byte) 0x29,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Double.NEGATIVE_INFINITY
+                (byte) 0x39,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff,
+                // 8 -> Double.NaN
+                (byte) 0x41,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f,
+                // 9 -> Double.POSITIVE_INFINITY
+                (byte) 0x49,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        double[][] results = new double[9][2];
+        int[] indices = new int[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readDouble(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readDouble(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readDouble(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readDouble(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readDouble(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readDouble(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readDouble(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    results[7][indices[7]++] = pi.readDouble(fieldId8);
+                    break;
+                case (int) fieldId9:
+                    results[8][indices[8]++] = pi.readDouble(fieldId9);
+                    break;
+                case (int) fieldId10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0, results[0][0]);
+        assertEquals(0.0, results[0][1]);
+        assertEquals(1.0, results[1][0]);
+        assertEquals(1.0, results[1][1]);
+        assertEquals(-1234.432, results[2][0]);
+        assertEquals(-1234.432, results[2][1]);
+        assertEquals(42.42, results[3][0]);
+        assertEquals(42.42, results[3][1]);
+        assertEquals(Double.MIN_NORMAL, results[4][0]);
+        assertEquals(Double.MIN_NORMAL, results[4][1]);
+        assertEquals(Double.MIN_VALUE, results[5][0]);
+        assertEquals(Double.MIN_VALUE, results[5][1]);
+        assertEquals(Double.NEGATIVE_INFINITY, results[6][0]);
+        assertEquals(Double.NEGATIVE_INFINITY, results[6][1]);
+        assertEquals(Double.NaN, results[7][0]);
+        assertEquals(Double.NaN, results[7][1]);
+        assertEquals(Double.POSITIVE_INFINITY, results[8][0]);
+        assertEquals(Double.POSITIVE_INFINITY, results[8][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new double[0]);
+        testRepeatedCompat(new double[]{0, 1, -1234.432, 42.42,
+                Double.MIN_NORMAL, Double.MIN_VALUE, Double.NEGATIVE_INFINITY, Double.NaN,
+                Double.POSITIVE_INFINITY,
+        });
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(double[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_DOUBLE;
+        final long fieldId = fieldFlags | ((long) 11 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.doubleFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        double[] result = new double[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readDouble(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.doubleFieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.doubleFieldRepeated[i], result[i]);
+        }
+    }
+
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_DOUBLE;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 10 -> 1
+                (byte) 0x52,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x1a,
+                (byte) 0x10,
+                (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e,
+                (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0,
+                (byte) 0x7d, (byte) 0x3f, (byte) 0x35, (byte) 0x5e,
+                (byte) 0xba, (byte) 0x49, (byte) 0x93, (byte) 0xc0,
+                // 4 -> 42.42
+                (byte) 0x22,
+                (byte) 0x10,
+                (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f,
+                (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40,
+                (byte) 0xf6, (byte) 0x28, (byte) 0x5c, (byte) 0x8f,
+                (byte) 0xc2, (byte) 0x35, (byte) 0x45, (byte) 0x40,
+                // 5 -> Double.MIN_NORMAL
+                (byte) 0x2a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x10, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x10,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Double.NEGATIVE_INFINITY
+                (byte) 0x3a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0xff,
+                // 8 -> Double.NaN
+                (byte) 0x42,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf8, (byte) 0x7f,
+                // 9 -> Double.POSITIVE_INFINITY
+                (byte) 0x4a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0xf0, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        double[][] results = new double[9][2];
+        int[] indices = new int[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readDouble(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readDouble(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readDouble(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readDouble(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readDouble(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readDouble(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readDouble(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    results[7][indices[7]++] = pi.readDouble(fieldId8);
+                    break;
+                case (int) fieldId9:
+                    results[8][indices[8]++] = pi.readDouble(fieldId9);
+                    break;
+                case (int) fieldId10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0, results[0][0]);
+        assertEquals(0.0, results[0][1]);
+        assertEquals(1.0, results[1][0]);
+        assertEquals(1.0, results[1][1]);
+        assertEquals(-1234.432, results[2][0]);
+        assertEquals(-1234.432, results[2][1]);
+        assertEquals(42.42, results[3][0]);
+        assertEquals(42.42, results[3][1]);
+        assertEquals(Double.MIN_NORMAL, results[4][0]);
+        assertEquals(Double.MIN_NORMAL, results[4][1]);
+        assertEquals(Double.MIN_VALUE, results[5][0]);
+        assertEquals(Double.MIN_VALUE, results[5][1]);
+        assertEquals(Double.NEGATIVE_INFINITY, results[6][0]);
+        assertEquals(Double.NEGATIVE_INFINITY, results[6][1]);
+        assertEquals(Double.NaN, results[7][0]);
+        assertEquals(Double.NaN, results[7][1]);
+        assertEquals(Double.POSITIVE_INFINITY, results[8][0]);
+        assertEquals(Double.POSITIVE_INFINITY, results[8][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new double[0]);
+        testPackedCompat(new double[]{0, 1, -1234.432, 42.42,
+                Double.MIN_NORMAL, Double.MIN_VALUE, Double.NEGATIVE_INFINITY, Double.NaN,
+                Double.POSITIVE_INFINITY,
+        });
+    }
+
+    /**
+     * Implementation of testPackedCompat with a given value.
+     */
+    private void testPackedCompat(double[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_DOUBLE;
+        final long fieldId = fieldFlags | ((long) 12 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.doubleFieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        double[] result = new double[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readDouble(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.doubleFieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.doubleFieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x09,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readInt(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_DOUBLE;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readDouble(fieldId1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId2:
+                        pi.readDouble(fieldId2);
+                        // don't fail, fixed64 is ok
+                        break;
+                    case (int) fieldId3:
+                        pi.readDouble(fieldId3);
+                        // don't fail, length delimited is ok (represents packed doubles)
+                        break;
+                    case (int) fieldId6:
+                        pi.readDouble(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java
new file mode 100644
index 0000000..f55d951
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamEnumTest.java
@@ -0,0 +1,570 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamEnumTest extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM;
+        final long fieldId = fieldFlags | ((long) 160 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.outsideField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        // Nano proto drops values that are outside the range, so compare against val
+        assertEquals(val, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_ENUM;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[]{});
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_ENUM;
+        final long fieldId = fieldFlags | ((long) 161 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.outsideFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        // Nano proto drops values that are outside the range, so compare against val
+        assertEquals(val.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(val[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_ENUM;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x32,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 4 -> MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 5 -> MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[]{});
+        testPackedCompat(new int[]{0, 1});
+
+        // Nano proto has a bug.  It gets the size with computeInt32SizeNoTag (correctly)
+        // but incorrectly uses writeRawVarint32 to write the value for negative numbers.
+        //testPackedCompat(new int[] { 0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE });
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_ENUM;
+        final long fieldId = fieldFlags | ((long) 162 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.outsideFieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        // Nano proto drops values that are outside the range, so compare against val
+        assertEquals(val.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(val[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_ENUM;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readInt(fieldId1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) fieldId2:
+                        pi.readInt(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readInt(fieldId3);
+                        // don't fail, length delimited is ok (represents packed enums)
+                        break;
+                    case (int) fieldId6:
+                        pi.readInt(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java
new file mode 100644
index 0000000..df68476
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed32Test.java
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamFixed32Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> 1
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32;
+        final long fieldId = fieldFlags | ((long) 90 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed32Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed32Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 6 -> 1
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[0]);
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32;
+        final long fieldId = fieldFlags | ((long) 91 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed32FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed32FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.fixed32FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FIXED32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> 1
+                (byte) 0x32,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x08,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[0]);
+        testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED32;
+        final long fieldId = fieldFlags | ((long) 92 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed32FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed32FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.fixed32FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x0d,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x04,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readInt(fieldId1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId2:
+                        pi.readInt(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readInt(fieldId3);
+                        // don't fail, length delimited is ok (represents packed fixed32)
+                        break;
+                    case (int) fieldId6:
+                        pi.readInt(fieldId6);
+                        // don't fail, fixed32 is ok
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java
new file mode 100644
index 0000000..af4130b
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFixed64Test.java
@@ -0,0 +1,649 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamFixed64Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> 1
+                (byte) 0x41,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[] results = new long[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+        assertEquals(Long.MIN_VALUE, results[5]);
+        assertEquals(Long.MAX_VALUE, results[6]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+        testReadCompat(Long.MIN_VALUE);
+        testReadCompat(Long.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(long val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64;
+        final long fieldId = fieldFlags | ((long) 100 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed64Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed64Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 8 -> 1
+                (byte) 0x41,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readLong(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new long[0]);
+        testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED64;
+        final long fieldId = fieldFlags | ((long) 101 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed64FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed64FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.fixed64FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FIXED64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x10,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 8 -> 1
+                (byte) 0x42,
+                (byte) 0x10,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x3a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readLong(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new long[0]);
+        testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FIXED64;
+        final long fieldId = fieldFlags | ((long) 102 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.fixed64FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.fixed64FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.fixed64FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x09,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readInt(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FIXED64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readLong(fieldId1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId2:
+                        pi.readLong(fieldId2);
+                        // don't fail, fixed64 is ok
+                        break;
+                    case (int) fieldId3:
+                        pi.readLong(fieldId3);
+                        // don't fail, length delimited is ok (represents packed fixed64)
+                        break;
+                    case (int) fieldId6:
+                        pi.readLong(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java
new file mode 100644
index 0000000..9bc07dc
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamFloatTest.java
@@ -0,0 +1,679 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamFloatTest extends TestCase {
+
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 10 -> 1
+                (byte) 0x55,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x1d,
+                (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4,
+                // 4 -> 42.42
+                (byte) 0x25,
+                (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42,
+                // 5 -> Float.MIN_NORMAL
+                (byte) 0x2d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Float.NEGATIVE_INFINITY
+                (byte) 0x3d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff,
+                // 8 -> Float.NaN
+                (byte) 0x45,
+                (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f,
+                // 9 -> Float.POSITIVE_INFINITY
+                (byte) 0x4d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        float[] results = new float[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readFloat(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readFloat(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readFloat(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readFloat(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5] = pi.readFloat(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6] = pi.readFloat(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    results[7] = pi.readFloat(fieldId8);
+                    break;
+                case (int) fieldId9:
+                    results[8] = pi.readFloat(fieldId9);
+                    break;
+                case (int) fieldId10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0f, results[0]);
+        assertEquals(1.0f, results[1]);
+        assertEquals(-1234.432f, results[2]);
+        assertEquals(42.42f, results[3]);
+        assertEquals(Float.MIN_NORMAL, results[4]);
+        assertEquals(Float.MIN_VALUE, results[5]);
+        assertEquals(Float.NEGATIVE_INFINITY, results[6]);
+        assertEquals(Float.NaN, results[7]);
+        assertEquals(Float.POSITIVE_INFINITY, results[8]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1234.432f);
+        testReadCompat(42.42f);
+        testReadCompat(Float.MIN_NORMAL);
+        testReadCompat(Float.MIN_VALUE);
+        testReadCompat(Float.NEGATIVE_INFINITY);
+        testReadCompat(Float.NaN);
+        testReadCompat(Float.POSITIVE_INFINITY);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(float val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT;
+        final long fieldId = fieldFlags | ((long) 20 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.floatField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        float result = 0.0f; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readFloat(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.floatField, result);
+    }
+
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FLOAT;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x1d,
+                (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4,
+                // 4 -> 42.42
+                (byte) 0x25,
+                (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42,
+                // 5 -> Float.MIN_NORMAL
+                (byte) 0x2d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Float.NEGATIVE_INFINITY
+                (byte) 0x3d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff,
+                // 8 -> Float.NaN
+                (byte) 0x45,
+                (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f,
+                // 9 -> Float.POSITIVE_INFINITY
+                (byte) 0x4d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f,
+
+                // 10 -> 1
+                (byte) 0x55,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x1d,
+                (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4,
+                // 4 -> 42.42
+                (byte) 0x25,
+                (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42,
+                // 5 -> Float.MIN_NORMAL
+                (byte) 0x2d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Float.NEGATIVE_INFINITY
+                (byte) 0x3d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff,
+                // 8 -> Float.NaN
+                (byte) 0x45,
+                (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f,
+                // 9 -> Float.POSITIVE_INFINITY
+                (byte) 0x4d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        float[][] results = new float[9][2];
+        int[] indices = new int[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readFloat(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readFloat(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readFloat(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readFloat(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readFloat(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readFloat(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readFloat(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    results[7][indices[7]++] = pi.readFloat(fieldId8);
+                    break;
+                case (int) fieldId9:
+                    results[8][indices[8]++] = pi.readFloat(fieldId9);
+                    break;
+                case (int) fieldId10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0f, results[0][0]);
+        assertEquals(0.0f, results[0][1]);
+        assertEquals(1.0f, results[1][0]);
+        assertEquals(1.0f, results[1][1]);
+        assertEquals(-1234.432f, results[2][0]);
+        assertEquals(-1234.432f, results[2][1]);
+        assertEquals(42.42f, results[3][0]);
+        assertEquals(42.42f, results[3][1]);
+        assertEquals(Float.MIN_NORMAL, results[4][0]);
+        assertEquals(Float.MIN_NORMAL, results[4][1]);
+        assertEquals(Float.MIN_VALUE, results[5][0]);
+        assertEquals(Float.MIN_VALUE, results[5][1]);
+        assertEquals(Float.NEGATIVE_INFINITY, results[6][0]);
+        assertEquals(Float.NEGATIVE_INFINITY, results[6][1]);
+        assertEquals(Float.NaN, results[7][0]);
+        assertEquals(Float.NaN, results[7][1]);
+        assertEquals(Float.POSITIVE_INFINITY, results[8][0]);
+        assertEquals(Float.POSITIVE_INFINITY, results[8][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new float[0]);
+        testRepeatedCompat(new float[]{0, 1, -1234.432f, 42.42f,
+                Float.MIN_NORMAL, Float.MIN_VALUE, Float.NEGATIVE_INFINITY, Float.NaN,
+                Float.POSITIVE_INFINITY,
+        });
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(float[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_FLOAT;
+        final long fieldId = fieldFlags | ((long) 21 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.floatFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        float[] result = new float[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readFloat(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.floatFieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.floatFieldRepeated[i], result[i]);
+        }
+    }
+
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FLOAT;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+        final long fieldId9 = fieldFlags | ((long) 9 & 0x0ffffffffL);
+        final long fieldId10 = fieldFlags | ((long) 10 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 10 -> 1
+                (byte) 0x52,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x3f,
+                // 3 -> -1234.432
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4,
+                (byte) 0xd3, (byte) 0x4d, (byte) 0x9a, (byte) 0xc4,
+                // 4 -> 42.42
+                (byte) 0x22,
+                (byte) 0x08,
+                (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42,
+                (byte) 0x14, (byte) 0xae, (byte) 0x29, (byte) 0x42,
+                // 5 -> Float.MIN_NORMAL
+                (byte) 0x2a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x00,
+                // 6 -> DOUBLE.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 7 -> Float.NEGATIVE_INFINITY
+                (byte) 0x3a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0xff,
+                // 8 -> Float.NaN
+                (byte) 0x42,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0xc0, (byte) 0x7f,
+                // 9 -> Float.POSITIVE_INFINITY
+                (byte) 0x4a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x80, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        float[][] results = new float[9][2];
+        int[] indices = new int[9];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readFloat(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readFloat(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readFloat(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readFloat(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readFloat(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readFloat(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readFloat(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    results[7][indices[7]++] = pi.readFloat(fieldId8);
+                    break;
+                case (int) fieldId9:
+                    results[8][indices[8]++] = pi.readFloat(fieldId9);
+                    break;
+                case (int) fieldId10:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+        assertEquals(0.0f, results[0][0]);
+        assertEquals(0.0f, results[0][1]);
+        assertEquals(1.0f, results[1][0]);
+        assertEquals(1.0f, results[1][1]);
+        assertEquals(-1234.432f, results[2][0]);
+        assertEquals(-1234.432f, results[2][1]);
+        assertEquals(42.42f, results[3][0]);
+        assertEquals(42.42f, results[3][1]);
+        assertEquals(Float.MIN_NORMAL, results[4][0]);
+        assertEquals(Float.MIN_NORMAL, results[4][1]);
+        assertEquals(Float.MIN_VALUE, results[5][0]);
+        assertEquals(Float.MIN_VALUE, results[5][1]);
+        assertEquals(Float.NEGATIVE_INFINITY, results[6][0]);
+        assertEquals(Float.NEGATIVE_INFINITY, results[6][1]);
+        assertEquals(Float.NaN, results[7][0]);
+        assertEquals(Float.NaN, results[7][1]);
+        assertEquals(Float.POSITIVE_INFINITY, results[8][0]);
+        assertEquals(Float.POSITIVE_INFINITY, results[8][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new float[0]);
+        testPackedCompat(new float[]{0, 1, -1234.432f, 42.42f,
+                Float.MIN_NORMAL, Float.MIN_VALUE, Float.NEGATIVE_INFINITY, Float.NaN,
+                Float.POSITIVE_INFINITY,
+        });
+    }
+
+    /**
+     * Implementation of testPackedCompat with a given value.
+     */
+    private void testPackedCompat(float[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_FLOAT;
+        final long fieldId = fieldFlags | ((long) 22 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.floatFieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        float[] result = new float[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readFloat(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.floatFieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.floatFieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x0d,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readInt(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_FLOAT;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x04,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readFloat(fieldId1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId2:
+                        pi.readFloat(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readFloat(fieldId3);
+                        // don't fail, length delimited is ok (represents packed floats)
+                        break;
+                    case (int) fieldId6:
+                        pi.readFloat(fieldId6);
+                        // don't fail, fixed32 is ok
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java
new file mode 100644
index 0000000..0065870
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt32Test.java
@@ -0,0 +1,565 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamInt32Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32;
+        final long fieldId = fieldFlags | ((long) 30 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int32Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int32Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[0]);
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT32;
+        final long fieldId = fieldFlags | ((long) 31 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int32FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int32FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.int32FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_INT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x32,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 4 -> MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 5 -> MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[0]);
+        testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT32;
+        final long fieldId = fieldFlags | ((long) 32 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int32FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int32FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.int32FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readInt(fieldId1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) fieldId2:
+                        pi.readInt(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readInt(fieldId3);
+                        // don't fail, length delimited is ok (represents packed int32)
+                        break;
+                    case (int) fieldId6:
+                        pi.readInt(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java
new file mode 100644
index 0000000..4d6d105
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamInt64Test.java
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamInt64Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 8 -> Long.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[] results = new long[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+        assertEquals(Long.MIN_VALUE, results[5]);
+        assertEquals(Long.MAX_VALUE, results[6]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+        testReadCompat(Long.MIN_VALUE);
+        testReadCompat(Long.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(long val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64;
+        final long fieldId = fieldFlags | ((long) 40 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int64Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int64Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 8 -> Long.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readLong(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new long[0]);
+        testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT64;
+        final long fieldId = fieldFlags | ((long) 41 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int64FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int64FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.int64FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_INT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+
+                // 8 -> Long.MAX_VALUE
+                (byte) 0x42,
+                (byte) 0x12,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x3a,
+                (byte) 0x12,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readLong(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new long[0]);
+        testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_INT64;
+        final long fieldId = fieldFlags | ((long) 42 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.int64FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.int64FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.int64FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readInt(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_INT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readLong(fieldId1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) fieldId2:
+                        pi.readLong(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readLong(fieldId3);
+                        // don't fail, length delimited is ok (represents packed int64)
+                        break;
+                    case (int) fieldId6:
+                        pi.readLong(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java
new file mode 100644
index 0000000..5e49eea
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamObjectTest.java
@@ -0,0 +1,507 @@
+/*
+ * 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamObjectTest extends TestCase {
+
+
+    class SimpleObject {
+        public char mChar;
+        public char mLargeChar;
+        public String mString;
+        public SimpleObject mNested;
+
+        void parseProto(ProtoInputStream pi) throws IOException {
+            final long uintFieldFlags =
+                    ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+            final long stringFieldFlags =
+                    ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
+            final long messageFieldFlags =
+                    ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+            final long charId = uintFieldFlags | ((long) 2 & 0x0ffffffffL);
+            final long largeCharId = uintFieldFlags | ((long) 5000 & 0x0ffffffffL);
+            final long stringId = stringFieldFlags | ((long) 4 & 0x0ffffffffL);
+            final long nestedId = messageFieldFlags | ((long) 5 & 0x0ffffffffL);
+
+            while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+                switch (pi.getFieldNumber()) {
+                    case (int) charId:
+                        mChar = (char) pi.readInt(charId);
+                        break;
+                    case (int) largeCharId:
+                        mLargeChar = (char) pi.readInt(largeCharId);
+                        break;
+                    case (int) stringId:
+                        mString = pi.readString(stringId);
+                        break;
+                    case (int) nestedId:
+                        long token = pi.start(nestedId);
+                        mNested = new SimpleObject();
+                        mNested.parseProto(pi);
+                        pi.end(token);
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Test reading an object with one char in it.
+     */
+    public void testObjectOneChar() throws IOException {
+        testObjectOneChar(0);
+        testObjectOneChar(1);
+        testObjectOneChar(5);
+    }
+
+    /**
+     * Implementation of testObjectOneChar for a given chunkSize.
+     */
+    private void testObjectOneChar(int chunkSize) throws IOException {
+        final long messageFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long messageId1 = messageFieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // Message 2 : { char 2 : 'c' }
+                (byte) 0x12, (byte) 0x02, (byte) 0x10, (byte) 0x63,
+                // Message 1 : { char 2 : 'b' }
+                (byte) 0x0a, (byte) 0x02, (byte) 0x10, (byte) 0x62,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+
+        SimpleObject result = null;
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) messageId1:
+                    final long token = pi.start(messageId1);
+                    result = new SimpleObject();
+                    result.parseProto(pi);
+                    pi.end(token);
+                    break;
+                case (int) messageId2:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertNotNull(result);
+        assertEquals('b', result.mChar);
+    }
+
+    /**
+     * Test reading an object with one multibyte unicode char in it.
+     */
+    public void testObjectOneLargeChar() throws IOException {
+        testObjectOneLargeChar(0);
+        testObjectOneLargeChar(1);
+        testObjectOneLargeChar(5);
+    }
+
+    /**
+     * Implementation of testObjectOneLargeChar for a given chunkSize.
+     */
+    private void testObjectOneLargeChar(int chunkSize) throws IOException {
+        final long messageFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long messageId1 = messageFieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // Message 2 : { char 5000 : '\u3110' }
+                (byte) 0x12, (byte) 0x05, (byte) 0xc0, (byte) 0xb8,
+                (byte) 0x02, (byte) 0x90, (byte) 0x62,
+                // Message 1 : { char 5000 : '\u3110' }
+                (byte) 0x0a, (byte) 0x05, (byte) 0xc0, (byte) 0xb8,
+                (byte) 0x02, (byte) 0x90, (byte) 0x62,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+
+        SimpleObject result = null;
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) messageId1:
+                    final long token = pi.start(messageId1);
+                    result = new SimpleObject();
+                    result.parseProto(pi);
+                    pi.end(token);
+                    break;
+                case (int) messageId2:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertNotNull(result);
+        assertEquals('\u3110', result.mLargeChar);
+    }
+
+    /**
+     * Test reading a char, then an object, then a char.
+     */
+    public void testObjectAndTwoChars() throws IOException {
+        testObjectAndTwoChars(0);
+        testObjectAndTwoChars(1);
+        testObjectAndTwoChars(5);
+    }
+
+    /**
+     * Implementation of testObjectAndTwoChars for a given chunkSize.
+     */
+    private void testObjectAndTwoChars(int chunkSize) throws IOException  {
+        final long uintFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+        final long messageFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long charId1 = uintFieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long charId4 = uintFieldFlags | ((long) 4 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 'a'
+                (byte) 0x08, (byte) 0x61,
+                // Message 1 : { char 2 : 'b' }
+                (byte) 0x12, (byte) 0x02, (byte) 0x10, (byte) 0x62,
+                // 4 -> 'c'
+                (byte) 0x20, (byte) 0x63,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+
+        SimpleObject obj = null;
+        char char1 = '\0';
+        char char4 = '\0';
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) charId1:
+                    char1 = (char) pi.readInt(charId1);
+                    break;
+                case (int) messageId2:
+                    final long token = pi.start(messageId2);
+                    obj = new SimpleObject();
+                    obj.parseProto(pi);
+                    pi.end(token);
+                    break;
+                case (int) charId4:
+                    char4 = (char) pi.readInt(charId4);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals('a', char1);
+        assertNotNull(obj);
+        assertEquals('b', obj.mChar);
+        assertEquals('c', char4);
+    }
+
+    /**
+     * Test reading a char, then an object with an int and a string in it, then a char.
+     */
+    public void testComplexObject() throws IOException {
+        testComplexObject(0);
+        testComplexObject(1);
+        testComplexObject(5);
+    }
+
+    /**
+     * Implementation of testComplexObject for a given chunkSize.
+     */
+    private void testComplexObject(int chunkSize) throws IOException  {
+        final long uintFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+        final long messageFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long charId1 = uintFieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long charId4 = uintFieldFlags | ((long) 4 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 'x'
+                (byte) 0x08, (byte) 0x78,
+                // begin object 2
+                (byte) 0x12, (byte) 0x10,
+                // 2 -> 'y'
+                (byte) 0x10, (byte) 0x79,
+                // 4 -> "abcdefghijkl"
+                (byte) 0x22, (byte) 0x0c,
+                (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66,
+                (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6a, (byte) 0x6b, (byte) 0x6c,
+                // 4 -> 'z'
+                (byte) 0x20, (byte) 0x7a,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+
+        SimpleObject obj = null;
+        char char1 = '\0';
+        char char4 = '\0';
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) charId1:
+                    char1 = (char) pi.readInt(charId1);
+                    break;
+                case (int) messageId2:
+                    final long token = pi.start(messageId2);
+                    obj = new SimpleObject();
+                    obj.parseProto(pi);
+                    pi.end(token);
+                    break;
+                case (int) charId4:
+                    char4 = (char) pi.readInt(charId4);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals('x', char1);
+        assertNotNull(obj);
+        assertEquals('y', obj.mChar);
+        assertEquals("abcdefghijkl", obj.mString);
+        assertEquals('z', char4);
+    }
+
+    /**
+     * Test reading 3 levels deep of objects.
+     */
+    public void testDeepObjects() throws IOException {
+        testDeepObjects(0);
+        testDeepObjects(1);
+        testDeepObjects(5);
+    }
+
+    /**
+     * Implementation of testDeepObjects for a given chunkSize.
+     */
+    private void testDeepObjects(int chunkSize) throws IOException  {
+        final long messageFieldFlags =
+                ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+        final long messageId2 = messageFieldFlags | ((long) 2 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // begin object id 2
+                (byte) 0x12, (byte) 0x1a,
+                // 2 -> 'a'
+                (byte) 0x10, (byte) 0x61,
+                // begin nested object id 5
+                (byte) 0x2a, (byte) 0x15,
+                // 5000 -> '\u3110'
+                (byte) 0xc0, (byte) 0xb8,
+                (byte) 0x02, (byte) 0x90, (byte) 0x62,
+                // begin nested object id 5
+                (byte) 0x2a, (byte) 0x0e,
+                // 4 -> "abcdefghijkl"
+                (byte) 0x22, (byte) 0x0c,
+                (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66,
+                (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x6a, (byte) 0x6b, (byte) 0x6c,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+
+        SimpleObject obj = null;
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) messageId2:
+                    final long token = pi.start(messageId2);
+                    obj = new SimpleObject();
+                    obj.parseProto(pi);
+                    pi.end(token);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertNotNull(obj);
+        assertEquals('a', obj.mChar);
+        assertNotNull(obj.mNested);
+        assertEquals('\u3110', obj.mNested.mLargeChar);
+        assertNotNull(obj.mNested.mNested);
+        assertEquals("abcdefghijkl", obj.mNested.mNested.mString);
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> {1}
+                (byte) 0x0a,
+                (byte) 0x01,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readInt(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_MESSAGE;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readBytes(fieldId1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId2:
+                        pi.readBytes(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readBytes(fieldId3);
+                        // don't fail, length delimited is ok
+                        break;
+                    case (int) fieldId6:
+                        pi.readBytes(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java
new file mode 100644
index 0000000..75c88a4
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed32Test.java
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamSFixed32Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> 1
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32;
+        final long fieldId = fieldFlags | ((long) 110 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed32Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed32Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 6 -> 1
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0d,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x15,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x25,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2d,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[0]);
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32;
+        final long fieldId = fieldFlags | ((long) 111 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed32FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed32FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sfixed32FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SFIXED32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> 1
+                (byte) 0x32,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x08,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x08,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[0]);
+        testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED32;
+        final long fieldId = fieldFlags | ((long) 112 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed32FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed32FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sfixed32FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x04,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readInt(fieldId1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId2:
+                        pi.readInt(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readInt(fieldId3);
+                        // don't fail, length delimited is ok (represents packed sfixed32)
+                        break;
+                    case (int) fieldId6:
+                        pi.readInt(fieldId6);
+                        // don't fail, fixed32 is ok
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java
new file mode 100644
index 0000000..4c65cf4
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSFixed64Test.java
@@ -0,0 +1,648 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamSFixed64Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 8 -> 1
+                (byte) 0x41,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[] results = new long[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+        assertEquals(Long.MIN_VALUE, results[5]);
+        assertEquals(Long.MAX_VALUE, results[6]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+        testReadCompat(Long.MIN_VALUE);
+        testReadCompat(Long.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(long val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64;
+        final long fieldId = fieldFlags | ((long) 120 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed64Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed64Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 8 -> 1
+                (byte) 0x41,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x09,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x19,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x21,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x29,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x31,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x39,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readLong(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new long[0]);
+        testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED64;
+        final long fieldId = fieldFlags | ((long) 121 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed64FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed64FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sfixed64FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SFIXED64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x10,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 8 -> 1
+                (byte) 0x42,
+                (byte) 0x10,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x10,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x80,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x3a,
+                (byte) 0x10,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readLong(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new long[0]);
+        testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SFIXED64;
+        final long fieldId = fieldFlags | ((long) 122 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sfixed64FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sfixed64FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sfixed64FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readInt(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SFIXED64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readLong(fieldId1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId2:
+                        pi.readLong(fieldId2);
+                        // don't fail, fixed32 is ok
+                        break;
+                    case (int) fieldId3:
+                        pi.readLong(fieldId3);
+                        // don't fail, length delimited is ok (represents packed sfixed64)
+                        break;
+                    case (int) fieldId6:
+                        pi.readLong(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java
new file mode 100644
index 0000000..6854cd8
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt32Test.java
@@ -0,0 +1,547 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamSInt32Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32;
+        final long fieldId = fieldFlags | ((long) 70 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint32Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint32Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[0]);
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT32;
+        final long fieldId = fieldFlags | ((long) 71 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint32FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint32FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sint32FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SINT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x02,
+                (byte) 0x02,
+                // 6 -> MAX_VALUE
+                (byte) 0x32,
+                (byte) 0x0a,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[0]);
+        testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT32;
+        final long fieldId = fieldFlags | ((long) 72 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint32FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint32FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sint32FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readInt(fieldId1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) fieldId2:
+                        pi.readInt(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readInt(fieldId3);
+                        // don't fail, length delimited is ok (represents packed sint32)
+                        break;
+                    case (int) fieldId6:
+                        pi.readInt(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java
new file mode 100644
index 0000000..c53e9d7
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamSInt64Test.java
@@ -0,0 +1,622 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamSInt64Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[] results = new long[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+        assertEquals(Long.MIN_VALUE, results[5]);
+        assertEquals(Long.MAX_VALUE, results[6]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+        testReadCompat(Long.MIN_VALUE);
+        testReadCompat(Long.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(long val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64;
+        final long fieldId = fieldFlags | ((long) 80 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint64Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint64Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x02,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readLong(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new long[0]);
+        testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT64;
+        final long fieldId = fieldFlags | ((long) 81 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint64FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint64FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sint64FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_SINT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x02,
+                (byte) 0x02,
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x42,
+                (byte) 0x0a,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x0f,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x3a,
+                (byte) 0x14,
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xfe, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readLong(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new long[0]);
+        testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_SINT64;
+        final long fieldId = fieldFlags | ((long) 82 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.sint64FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.sint64FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.sint64FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readInt(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_SINT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readLong(fieldId1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) fieldId2:
+                        pi.readLong(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readLong(fieldId3);
+                        // don't fail, length delimited is ok (represents packed sint64)
+                        break;
+                    case (int) fieldId6:
+                        pi.readLong(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java
new file mode 100644
index 0000000..816d5f9
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamStringTest.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamStringTest extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> null - default value, not written
+                // 2 -> "" - default value, not written
+                // 3 -> "abcd\u3110!"
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64,
+                (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21,
+                // 5 -> "Hi"
+                (byte) 0x2a,
+                (byte) 0x02,
+                (byte) 0x48, (byte) 0x69,
+                // 4 -> "Hi"
+                (byte) 0x22,
+                (byte) 0x02,
+                (byte) 0x48, (byte) 0x69,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        String[] results = new String[4];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0] = pi.readString(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readString(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readString(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readString(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertNull(results[0]);
+        assertNull(results[1]);
+        assertEquals("abcd\u3110!", results[2]);
+        assertEquals("Hi", results[3]);
+    }
+
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat("");
+        testReadCompat("abcd\u3110!");
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(String val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
+        final long fieldId = fieldFlags | ((long) 140 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.stringField = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        String result = "";
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readString(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.stringField, result);
+    }
+
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_STRING;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> null - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x00,
+                // 2 -> "" - default value, written when repeated
+                (byte) 0x12,
+                (byte) 0x00,
+                // 3 -> "abcd\u3110!"
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64,
+                (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21,
+                // 4 -> "Hi"
+                (byte) 0x22,
+                (byte) 0x02,
+                (byte) 0x48, (byte) 0x69,
+
+                // 5 -> "Hi"
+                (byte) 0x2a,
+                (byte) 0x02,
+                (byte) 0x48, (byte) 0x69,
+
+                // 1 -> null - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x00,
+                // 2 -> "" - default value, written when repeated
+                (byte) 0x12,
+                (byte) 0x00,
+                // 3 -> "abcd\u3110!"
+                (byte) 0x1a,
+                (byte) 0x08,
+                (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64,
+                (byte) 0xe3, (byte) 0x84, (byte) 0x90, (byte) 0x21,
+                // 4 -> "Hi"
+                (byte) 0x22,
+                (byte) 0x02,
+                (byte) 0x48, (byte) 0x69,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        String[][] results = new String[4][2];
+        int[] indices = new int[4];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readString(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readString(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readString(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readString(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals("", results[0][0]);
+        assertEquals("", results[0][1]);
+        assertEquals("", results[1][0]);
+        assertEquals("", results[1][1]);
+        assertEquals("abcd\u3110!", results[2][0]);
+        assertEquals("abcd\u3110!", results[2][1]);
+        assertEquals("Hi", results[3][0]);
+        assertEquals("Hi", results[3][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new String[0]);
+        testRepeatedCompat(new String[]{"", "abcd\u3110!", "Hi"});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(String[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_STRING;
+        final long fieldId = fieldFlags | ((long) 141 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.stringFieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        String[] result = new String[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readString(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.stringFieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.stringFieldRepeated[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> {1}
+                (byte) 0x0a,
+                (byte) 0x01,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readInt(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_STRING;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readString(fieldId1);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId2:
+                        pi.readString(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readString(fieldId3);
+                        // don't fail, length delimited is ok (represents packed booleans)
+                        break;
+                    case (int) fieldId6:
+                        pi.readString(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java
new file mode 100644
index 0000000..50fc537
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt32Test.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamUInt32Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[] results = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(int val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+        final long fieldId = fieldFlags | ((long) 50 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint32Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint32Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 6 -> MAX_VALUE
+                (byte) 0x30,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new int[0]);
+        testRepeatedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT32;
+        final long fieldId = fieldFlags | ((long) 51 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint32FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint32FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.uint32FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_UINT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+
+                // 6 -> MAX_VALUE
+                (byte) 0x32,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 4 -> MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 5 -> MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        int[][] results = new int[5][2];
+        int[] indices = new int[5];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readInt(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readInt(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readInt(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readInt(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readInt(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new int[0]);
+        testPackedCompat(new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(int[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT32;
+        final long fieldId = fieldFlags | ((long) 52 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint32FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        int[] result = new int[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readInt(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint32FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.uint32FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readLong(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT32;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readInt(fieldId1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) fieldId2:
+                        pi.readInt(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readInt(fieldId3);
+                        // don't fail, length delimited is ok (represents packed uint32)
+                        break;
+                    case (int) fieldId6:
+                        pi.readInt(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java
new file mode 100644
index 0000000..20969e9
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoInputStreamUInt64Test.java
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoStream;
+import android.util.proto.WireTypeMismatchException;
+
+import com.android.test.protoinputstream.nano.Test;
+
+import com.google.protobuf.nano.MessageNano;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class ProtoInputStreamUInt64Test extends TestCase {
+
+    public void testRead() throws IOException {
+        testRead(0);
+        testRead(1);
+        testRead(5);
+    }
+
+    private void testRead(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, not written
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[] results = new long[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    fail("Should never reach this");
+                    break;
+                case (int) fieldId2:
+                    results[1] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0]);
+        assertEquals(1, results[1]);
+        assertEquals(-1, results[2]);
+        assertEquals(Integer.MIN_VALUE, results[3]);
+        assertEquals(Integer.MAX_VALUE, results[4]);
+        assertEquals(Long.MIN_VALUE, results[5]);
+        assertEquals(Long.MAX_VALUE, results[6]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testReadCompat() throws Exception {
+        testReadCompat(0);
+        testReadCompat(1);
+        testReadCompat(-1);
+        testReadCompat(Integer.MIN_VALUE);
+        testReadCompat(Integer.MAX_VALUE);
+        testReadCompat(Long.MIN_VALUE);
+        testReadCompat(Long.MAX_VALUE);
+    }
+
+    /**
+     * Implementation of testReadCompat with a given value.
+     */
+    private void testReadCompat(long val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64;
+        final long fieldId = fieldFlags | ((long) 60 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint64Field = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long result = 0; // start off with default value
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint64Field, result);
+    }
+
+    public void testRepeated() throws IOException {
+        testRepeated(0);
+        testRepeated(1);
+        testRepeated(5);
+    }
+
+    private void testRepeated(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x40,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x08,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x10,
+                (byte) 0x01,
+                // 3 -> -1
+                (byte) 0x18,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x20,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x28,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x30,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x38,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readLong(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testRepeatedCompat() throws Exception {
+        testRepeatedCompat(new long[0]);
+        testRepeatedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testRepeatedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT64;
+        final long fieldId = fieldFlags | ((long) 61 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint64FieldRepeated = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint64FieldRepeated.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.uint64FieldRepeated[i], result[i]);
+        }
+    }
+
+    public void testPacked() throws IOException {
+        testPacked(0);
+        testPacked(1);
+        testPacked(5);
+    }
+
+    private void testPacked(int chunkSize) throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_PACKED | ProtoStream.FIELD_TYPE_UINT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId4 = fieldFlags | ((long) 4 & 0x0ffffffffL);
+        final long fieldId5 = fieldFlags | ((long) 5 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+        final long fieldId7 = fieldFlags | ((long) 7 & 0x0ffffffffL);
+        final long fieldId8 = fieldFlags | ((long) 8 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 0 - default value, written when repeated
+                (byte) 0x0a,
+                (byte) 0x02,
+                (byte) 0x00,
+                (byte) 0x00,
+                // 2 -> 1
+                (byte) 0x12,
+                (byte) 0x02,
+                (byte) 0x01,
+                (byte) 0x01,
+
+                // 8 -> Integer.MAX_VALUE
+                (byte) 0x42,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 3 -> -1
+                (byte) 0x1a,
+                (byte) 0x14,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 4 -> Integer.MIN_VALUE
+                (byte) 0x22,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x01,
+
+                // 5 -> Integer.MAX_VALUE
+                (byte) 0x2a,
+                (byte) 0x0a,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x07,
+
+                // 6 -> Long.MIN_VALUE
+                (byte) 0x32,
+                (byte) 0x14,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
+                (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x01,
+
+                // 7 -> Long.MAX_VALUE
+                (byte) 0x3a,
+                (byte) 0x12,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff,
+                (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0x7f,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream, chunkSize);
+        long[][] results = new long[7][2];
+        int[] indices = new int[7];
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId1:
+                    results[0][indices[0]++] = pi.readLong(fieldId1);
+                    break;
+                case (int) fieldId2:
+                    results[1][indices[1]++] = pi.readLong(fieldId2);
+                    break;
+                case (int) fieldId3:
+                    results[2][indices[2]++] = pi.readLong(fieldId3);
+                    break;
+                case (int) fieldId4:
+                    results[3][indices[3]++] = pi.readLong(fieldId4);
+                    break;
+                case (int) fieldId5:
+                    results[4][indices[4]++] = pi.readLong(fieldId5);
+                    break;
+                case (int) fieldId6:
+                    results[5][indices[5]++] = pi.readLong(fieldId6);
+                    break;
+                case (int) fieldId7:
+                    results[6][indices[6]++] = pi.readLong(fieldId7);
+                    break;
+                case (int) fieldId8:
+                    // Intentionally don't read the data. Parse should continue normally
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+        stream.close();
+
+        assertEquals(0, results[0][0]);
+        assertEquals(0, results[0][1]);
+        assertEquals(1, results[1][0]);
+        assertEquals(1, results[1][1]);
+        assertEquals(-1, results[2][0]);
+        assertEquals(-1, results[2][1]);
+        assertEquals(Integer.MIN_VALUE, results[3][0]);
+        assertEquals(Integer.MIN_VALUE, results[3][1]);
+        assertEquals(Integer.MAX_VALUE, results[4][0]);
+        assertEquals(Integer.MAX_VALUE, results[4][1]);
+        assertEquals(Long.MIN_VALUE, results[5][0]);
+        assertEquals(Long.MIN_VALUE, results[5][1]);
+        assertEquals(Long.MAX_VALUE, results[6][0]);
+        assertEquals(Long.MAX_VALUE, results[6][1]);
+    }
+
+    /**
+     * Test that reading with ProtoInputStream matches, and can read the output of standard proto.
+     */
+    public void testPackedCompat() throws Exception {
+        testPackedCompat(new long[0]);
+        testPackedCompat(new long[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE});
+    }
+
+    /**
+     * Implementation of testRepeatedCompat with a given value.
+     */
+    private void testPackedCompat(long[] val) throws Exception {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_REPEATED | ProtoStream.FIELD_TYPE_UINT64;
+        final long fieldId = fieldFlags | ((long) 62 & 0x0ffffffffL);
+
+        final Test.All all = new Test.All();
+        all.uint64FieldPacked = val;
+
+        final byte[] proto = MessageNano.toByteArray(all);
+
+        final ProtoInputStream pi = new ProtoInputStream(proto);
+        final Test.All readback = Test.All.parseFrom(proto);
+
+        long[] result = new long[val.length];
+        int index = 0;
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            switch (pi.getFieldNumber()) {
+                case (int) fieldId:
+                    result[index++] = pi.readLong(fieldId);
+                    break;
+                default:
+                    fail("Unexpected field id " + pi.getFieldNumber());
+            }
+        }
+
+        assertEquals(readback.uint64FieldPacked.length, result.length);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(readback.uint64FieldPacked[i], result[i]);
+        }
+    }
+
+    /**
+     * Test that using the wrong read method throws an exception
+     */
+    public void testBadReadType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+        };
+
+        ProtoInputStream pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readFloat(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readDouble(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readInt(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBoolean(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readBytes(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        pi = new ProtoInputStream(protobuf);
+        pi.isNextField(fieldId1);
+        try {
+            pi.readString(fieldId1);
+            fail("Should have throw IllegalArgumentException");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+    }
+
+    /**
+     * Test that unexpected wrong wire types will throw an exception
+     */
+    public void testBadWireType() throws IOException {
+        final long fieldFlags = ProtoStream.FIELD_COUNT_SINGLE | ProtoStream.FIELD_TYPE_UINT64;
+
+        final long fieldId1 = fieldFlags | ((long) 1 & 0x0ffffffffL);
+        final long fieldId2 = fieldFlags | ((long) 2 & 0x0ffffffffL);
+        final long fieldId3 = fieldFlags | ((long) 3 & 0x0ffffffffL);
+        final long fieldId6 = fieldFlags | ((long) 6 & 0x0ffffffffL);
+
+        final byte[] protobuf = new byte[]{
+                // 1 : varint -> 1
+                (byte) 0x08,
+                (byte) 0x01,
+                // 2 : fixed64 -> 0x1
+                (byte) 0x11,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                // 3 : length delimited -> { 1 }
+                (byte) 0x1a,
+                (byte) 0x01,
+                (byte) 0x01,
+                // 6 : fixed32
+                (byte) 0x35,
+                (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        };
+
+        InputStream stream = new ByteArrayInputStream(protobuf);
+        final ProtoInputStream pi = new ProtoInputStream(stream);
+
+        while (pi.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+            try {
+                switch (pi.getFieldNumber()) {
+                    case (int) fieldId1:
+                        pi.readLong(fieldId1);
+                        // don't fail, varint is ok
+                        break;
+                    case (int) fieldId2:
+                        pi.readLong(fieldId2);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    case (int) fieldId3:
+                        pi.readLong(fieldId3);
+                        // don't fail, length delimited is ok (represents packed uint64)
+                        break;
+                    case (int) fieldId6:
+                        pi.readLong(fieldId6);
+                        fail("Should have thrown a WireTypeMismatchException");
+                        break;
+                    default:
+                        fail("Unexpected field id " + pi.getFieldNumber());
+                }
+            } catch (WireTypeMismatchException wtme) {
+                // good
+            }
+        }
+        stream.close();
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoTests.java b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoTests.java
new file mode 100644
index 0000000..cdf6ae2
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/ProtoTests.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 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.test.protoinputstream;
+
+import junit.framework.TestSuite;
+
+public class ProtoTests {
+    public static TestSuite suite() {
+        TestSuite suite = new TestSuite(ProtoTests.class.getName());
+
+        suite.addTestSuite(ProtoInputStreamDoubleTest.class);
+        suite.addTestSuite(ProtoInputStreamFloatTest.class);
+        suite.addTestSuite(ProtoInputStreamInt32Test.class);
+        suite.addTestSuite(ProtoInputStreamInt64Test.class);
+        suite.addTestSuite(ProtoInputStreamUInt32Test.class);
+        suite.addTestSuite(ProtoInputStreamUInt64Test.class);
+        suite.addTestSuite(ProtoInputStreamSInt32Test.class);
+        suite.addTestSuite(ProtoInputStreamSInt64Test.class);
+        suite.addTestSuite(ProtoInputStreamFixed32Test.class);
+        suite.addTestSuite(ProtoInputStreamFixed64Test.class);
+        suite.addTestSuite(ProtoInputStreamSFixed32Test.class);
+        suite.addTestSuite(ProtoInputStreamSFixed64Test.class);
+        suite.addTestSuite(ProtoInputStreamBoolTest.class);
+        suite.addTestSuite(ProtoInputStreamStringTest.class);
+        suite.addTestSuite(ProtoInputStreamBytesTest.class);
+        suite.addTestSuite(ProtoInputStreamEnumTest.class);
+        suite.addTestSuite(ProtoInputStreamObjectTest.class);
+
+        return suite;
+    }
+}
diff --git a/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/test.proto b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/test.proto
new file mode 100644
index 0000000..9ff1d7e
--- /dev/null
+++ b/tests/ProtoInputStreamTests/src/com/android/test/protoinputstream/test.proto
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package com.android.test.protoinputstream;
+
+/**
+ * Enum that outside the scope of any classes.
+ */
+enum Outside {
+    OUTSIDE_0 = 0;
+    OUTSIDE_1 = 1;
+};
+
+/**
+ * Message that is recursive.
+ */
+message Nested {
+    optional int32 data = 10001;
+    optional Nested nested = 10002;
+};
+
+/**
+ * Message with all of the field types.
+ */
+message All {
+    /**
+     * Enum that is inside the scope of a class.
+     */
+    enum Inside {
+        option allow_alias = true;
+        INSIDE_0 = 0;
+        INSIDE_1 = 1;
+        INSIDE_1A = 1;
+    };
+
+    optional double double_field = 10;
+    repeated double double_field_repeated = 11;
+    repeated double double_field_packed = 12 [packed=true];
+
+    optional float float_field = 20;
+    repeated float float_field_repeated = 21;
+    repeated float float_field_packed = 22 [packed=true];
+
+    optional int32 int32_field = 30;
+    repeated int32 int32_field_repeated = 31;
+    repeated int32 int32_field_packed = 32 [packed=true];
+
+    optional int64 int64_field = 40;
+    repeated int64 int64_field_repeated = 41;
+    repeated int64 int64_field_packed = 42 [packed=true];
+
+    optional uint32 uint32_field = 50;
+    repeated uint32 uint32_field_repeated = 51;
+    repeated uint32 uint32_field_packed = 52 [packed=true];
+
+    optional uint64 uint64_field = 60;
+    repeated uint64 uint64_field_repeated = 61;
+    repeated uint64 uint64_field_packed = 62 [packed=true];
+
+    optional sint32 sint32_field = 70;
+    repeated sint32 sint32_field_repeated = 71;
+    repeated sint32 sint32_field_packed = 72 [packed=true];
+
+    optional sint64 sint64_field = 80;
+    repeated sint64 sint64_field_repeated = 81;
+    repeated sint64 sint64_field_packed = 82 [packed=true];
+
+    optional fixed32 fixed32_field = 90;
+    repeated fixed32 fixed32_field_repeated = 91;
+    repeated fixed32 fixed32_field_packed = 92 [packed=true];
+
+    optional fixed64 fixed64_field = 100;
+    repeated fixed64 fixed64_field_repeated = 101;
+    repeated fixed64 fixed64_field_packed = 102 [packed=true];
+
+    optional sfixed32 sfixed32_field = 110;
+    repeated sfixed32 sfixed32_field_repeated = 111;
+    repeated sfixed32 sfixed32_field_packed = 112 [packed=true];
+
+    optional sfixed64 sfixed64_field = 120;
+    repeated sfixed64 sfixed64_field_repeated = 121;
+    repeated sfixed64 sfixed64_field_packed = 122 [packed=true];
+
+    optional bool bool_field = 130;
+    repeated bool bool_field_repeated = 131;
+    repeated bool bool_field_packed = 132 [packed=true];
+
+    optional string string_field = 140;
+    repeated string string_field_repeated = 141;
+
+    optional bytes bytes_field = 150;
+    repeated bytes bytes_field_repeated = 151;
+
+    optional Outside outside_field = 160;
+    repeated Outside outside_field_repeated = 161;
+    repeated Outside outside_field_packed = 162 [packed=true];
+
+    optional Nested nested_field = 170;
+    repeated Nested nested_field_repeated = 171;
+};
diff --git a/tests/net/java/android/net/NetworkStackTest.java b/tests/net/java/android/net/NetworkStackTest.java
new file mode 100644
index 0000000..f7c6c99
--- /dev/null
+++ b/tests/net/java/android/net/NetworkStackTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 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 static android.Manifest.permission.NETWORK_STACK;
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK;
+import static android.net.NetworkStack.checkNetworkStackPermission;
+import static android.net.NetworkStack.checkNetworkStackPermissionOr;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class NetworkStackTest {
+    private static final String [] OTHER_PERMISSION = {"otherpermission1", "otherpermission2"};
+
+    @Mock Context mCtx;
+
+    @Before public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void testCheckNetworkStackPermission() throws Exception {
+        when(mCtx.checkCallingOrSelfPermission(eq(NETWORK_STACK))).thenReturn(PERMISSION_GRANTED);
+        when(mCtx.checkCallingOrSelfPermission(eq(PERMISSION_MAINLINE_NETWORK_STACK)))
+                .thenReturn(PERMISSION_DENIED);
+        checkNetworkStackPermission(mCtx);
+        checkNetworkStackPermissionOr(mCtx, OTHER_PERMISSION);
+
+        when(mCtx.checkCallingOrSelfPermission(eq(NETWORK_STACK))).thenReturn(PERMISSION_DENIED);
+        when(mCtx.checkCallingOrSelfPermission(eq(PERMISSION_MAINLINE_NETWORK_STACK)))
+                .thenReturn(PERMISSION_GRANTED);
+        checkNetworkStackPermission(mCtx);
+        checkNetworkStackPermissionOr(mCtx, OTHER_PERMISSION);
+
+        when(mCtx.checkCallingOrSelfPermission(any())).thenReturn(PERMISSION_DENIED);
+
+        try {
+            checkNetworkStackPermissionOr(mCtx, OTHER_PERMISSION);
+        } catch (SecurityException e) {
+            // Expect to get a SecurityException
+            return;
+        }
+
+        fail("Expect fail but permission granted.");
+    }
+}
diff --git a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java
index 68ff777..22a2c94 100644
--- a/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceRefcountedResourceTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Matchers.eq;
@@ -134,11 +135,11 @@
         IBinder binderMock = mock(IBinder.class);
         doThrow(new RemoteException()).when(binderMock).linkToDeath(anyObject(), anyInt());
 
-        RefcountedResource<IResource> refcountedResource = getTestRefcountedResource(binderMock);
-
-        // Verify that cleanup is performed (Spy limitations prevent verification of method calls
-        // for binder death scenario; check refcount to determine if cleanup was performed.)
-        assertEquals(-1, refcountedResource.mRefCount);
+        try {
+            getTestRefcountedResource(binderMock);
+            fail("Expected exception to propogate when binder fails to link to death");
+        } catch (RuntimeException expected) {
+        }
     }
 
     @Test