diff options
73 files changed, 10728 insertions, 252 deletions
diff --git a/api/current.txt b/api/current.txt index 661b9c6ed827..4bbae3b14f7d 100644 --- a/api/current.txt +++ b/api/current.txt @@ -51676,7 +51676,7 @@ package android.view { 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); @@ -51691,7 +51691,7 @@ package android.view { 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/system-current.txt b/api/system-current.txt index 76b8f6610590..f2423437c3fc 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -553,7 +553,7 @@ package android.app { } 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 @@ package android.service.notification { 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 7121a549176a..c3215a6867ae 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -14,6 +14,7 @@ package android { 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 @@ package android.app { } 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 @@ package android.service.notification { 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 @@ package android.telephony { 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 @@ package android.util.proto { 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/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 54fe65db499c..9079ace4b8a3 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -132,6 +132,7 @@ import android.widget.AdapterView; 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 @@ public class Activity extends ContextThemeWrapper 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 395c867de9d7..b80f7813640c 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -986,6 +986,8 @@ public class ActivityManager { 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 @@ public class ActivityManager { */ @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 @@ public class ActivityManager { * 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 @@ public class ActivityManager { */ @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 @@ public class ActivityManager { * 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 @@ public class ActivityManager { * @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 @@ public class ActivityManager { mColorBackground = colorBackground; mStatusBarColor = statusBarColor; mNavigationBarColor = navigationBarColor; + mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent; + mEnsureNavigationBarContrastWhenTransparent = + ensureNavigationBarContrastWhenTransparent; } /** @@ -1092,6 +1099,9 @@ public class ActivityManager { mColorBackground = other.mColorBackground; mStatusBarColor = other.mStatusBarColor; mNavigationBarColor = other.mNavigationBarColor; + mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent; + mEnsureNavigationBarContrastWhenTransparent = + other.mEnsureNavigationBarContrastWhenTransparent; } /** @@ -1114,6 +1124,9 @@ public class ActivityManager { if (other.mNavigationBarColor != 0) { mNavigationBarColor = other.mNavigationBarColor; } + mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent; + mEnsureNavigationBarContrastWhenTransparent = + other.mEnsureNavigationBarContrastWhenTransparent; } private TaskDescription(Parcel source) { @@ -1272,6 +1285,37 @@ public class ActivityManager { 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 @@ public class ActivityManager { 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 @@ public class ActivityManager { 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 @@ public class ActivityManager { 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 7884872a7ef3..b3c2429004b8 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -70,9 +70,9 @@ interface INotificationManager 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/NotificationManager.java b/core/java/android/app/NotificationManager.java index d54aca89c50d..dd39376f80ca 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1206,10 +1206,10 @@ public class NotificationManager { */ @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 @@ public class NotificationManager { * @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 @@ public class NotificationManager { * @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/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 7fe840c11dfa..a71f7d2c6455 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -30,7 +30,6 @@ import android.annotation.UnsupportedAppUsage; 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 @@ public class PackageInstaller { * * <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 @@ public class PackageInstaller { } /** - * 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 270aea851791..b2b4e0e2edfb 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1580,8 +1580,8 @@ public class PackageParser { 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) @@ -1618,7 +1618,13 @@ public class PackageParser { "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. } } diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index dc1d052da64f..69462ab99483 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -36,7 +36,9 @@ import java.io.IOException; */ 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 @@ public final class ApkAssets { @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 b5ec0f9f3712..b7bc8229fa45 100644 --- a/core/java/android/content/res/StringBlock.java +++ b/core/java/android/content/res/StringBlock.java @@ -18,13 +18,34 @@ package android.content.res; 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 @@ final class StringBlock { 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 @@ final class StringBlock { } } + @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/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java index 53503f47ca74..e56b6e0a33b8 100644 --- a/core/java/android/os/GraphicsEnvironment.java +++ b/core/java/android/os/GraphicsEnvironment.java @@ -188,11 +188,16 @@ public class GraphicsEnvironment { 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 bd70f23c8b5d..ab19fd6e8ca1 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -646,9 +646,14 @@ public class ZygoteProcess { 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/provider/Settings.java b/core/java/android/provider/Settings.java index 6e897975963f..e3b2d898f9b6 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -955,6 +955,20 @@ public final class Settings { "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 8bb5f97ca878..5977bafd7cf1 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -53,5 +53,5 @@ oneway interface INotificationListener 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 b4fd39701233..cafeb87691bd 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -220,10 +220,10 @@ public abstract class NotificationAssistantService extends NotificationListenerS /** * 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 @@ public abstract class NotificationAssistantService extends NotificationListenerS } @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 abstract class NotificationAssistantService extends NotificationListenerS 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 @@ public abstract class NotificationAssistantService extends NotificationListenerS 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 016f4aacef2a..3ec21e39e514 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 @@ public abstract class NotificationListenerService extends Service { } @Override - public void onCapabilitiesChanged() { + public void onAllowedAdjustmentsChanged() { // no-op in the listener } @@ -1680,6 +1681,7 @@ public abstract class NotificationListenerService extends Service { * * @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 cd2b6ce3dc67..c290dffc42c9 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 @@ import java.util.ArrayList; * * @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 000000000000..cf9f0772ac2d --- /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/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 2896bd049e7c..c50a3aa8ac7c 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -932,7 +932,8 @@ public final class ViewTreeObserver { * @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 @@ public final class ViewTreeObserver { * @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 3544a8733c68..a9463e998bbb 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -2329,6 +2329,70 @@ public abstract class Window { 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/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java index 1c0030d20c63..d945e139dd0f 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.FLAG_TRANSLUCENT_STATUS; 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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind // 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 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind 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 04559e4e55c6..16d6c52d82ea 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.AudioManager; 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private boolean mForcedStatusBarColor = false; private boolean mForcedNavigationBarColor = false; + boolean mEnsureStatusBarContrastWhenTransparent; + boolean mEnsureNavigationBarContrastWhenTransparent; + @UnsupportedAppUsage private CharSequence mTitle = null; @@ -2439,6 +2443,7 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { 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/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b94eb1626e37..cc3b3a4c7ccb 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 2446402be93f..2152af55d5b6 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/attrs.xml b/core/res/res/values/attrs.xml index 9d48fe384f3f..a5104242c71c 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/config.xml b/core/res/res/values/config.xml index fe4fd133dbef..8fcdc7b1d748 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3254,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/symbols.xml b/core/res/res/values/symbols.xml index dc8c62c9489e..8cdce6bd6533 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" /> diff --git a/media/OWNERS b/media/OWNERS index 72c89529974b..a33a990f5ba4 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 db33e8239175..72c18f6148f0 100644 --- a/media/apex/java/android/media/MediaPlayer2.java +++ b/media/apex/java/android/media/MediaPlayer2.java @@ -546,7 +546,7 @@ public class MediaPlayer2 implements AutoCloseable, AudioRouting { @Override void process() { if (getState() == PLAYER_STATE_PLAYING) { - pause(); + native_pause(); } playNextDataSource(); } 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 0a20eaa0b888..a371a1d8db01 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.Intent; 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 @@ public class CarFacetButton extends LinearLayout { 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 7811a1caeb88..d20038db9151 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.Context; 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 @@ import javax.inject.Singleton; @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 class CarFacetButtonController { 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 @@ public class CarFacetButtonController { * @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 @@ public class CarFacetButtonController { 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 @@ public class CarFacetButtonController { } } - 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 @@ public class CarFacetButtonController { } 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 9bcc1ab90e47..44e8874f8386 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 @@ public class CarStatusBar extends StatusBar implements 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/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java index 530c73a2448b..fb5c16b92930 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 @@ package com.android.settingslib.fuelgauge; 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 @@ import android.util.Slog; 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 @@ public class BatterySaverUtils { } 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 @@ public class BatterySaverUtils { && 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 @@ public class BatterySaverUtils { /** * 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/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 10f727bc7189..e92aa519b9b7 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.NotificationManager; 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 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { 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 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { 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 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { 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/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml b/packages/overlays/IconPackCircularAndroidOverlay/res/drawable/ic_bluetooth_share_icon.xml index 380ff346c6f2..4d844a1c4e0f 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 452a032c49e9..19731249bfc7 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 8719f155f94a..df79827ce624 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 09643e606350..58800c8d29be 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 d0f9d9b567f3..feed70c49138 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 3d270b3fce42..5e1a5f20c6d6 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 704ff2ee2aa3..86fd47bfc63f 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 0402b8fb9285..fdc01e0c1093 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (!unlinked) { Slog.w(TAG, "unlinking vulture from death failed for " + mActivityToken); } + mClientVulture = null; } } @@ -1243,18 +1245,55 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * when necessary. */ public void logContextCommitted() { - mHandler.sendMessage(obtainMessage( - Session::doLogContextCommitted, this)); + mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this)); + } + + private void handleLogContextCommitted() { + final FillResponse lastResponse; + synchronized (mLock) { + 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 doLogContextCommitted() { + private void logContextCommitted(@Nullable ArrayList<AutofillId> detectedFieldIds, + @Nullable ArrayList<FieldClassification> detectedFieldClassifications) { synchronized (mLock) { - logContextCommittedLocked(); + 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - // 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 class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState + 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 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState 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/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index dec47a1082fc..7f1b25ca3ca3 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 @@ public class NotificationManagerService extends SystemService { } @VisibleForTesting + void setZenHelper(ZenModeHelper zenHelper) { + mZenModeHelper = zenHelper; + } + + @VisibleForTesting void setIsAutomotive(boolean isAutomotive) { mIsAutomotive = isAutomotive; } @@ -2856,7 +2861,7 @@ public class NotificationManagerService extends SystemService { } @Override - public List<String> getAllowedAssistantCapabilities(String pkg) { + public List<String> getAllowedAssistantAdjustments(String pkg) { checkCallerIsSystemOrSameApp(pkg); if (!isCallerSystemOrPhone() @@ -2864,11 +2869,11 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } @Override - public void disallowAssistantCapability(String adjustmentType) { + public void disallowAssistantAdjustment(String adjustmentType) { checkCallerIsSystemOrSystemUiOrShell(); mAssistants.disallowAdjustmentType(adjustmentType); @@ -3563,7 +3568,7 @@ public class NotificationManagerService extends SystemService { 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 @@ public class NotificationManagerService extends SystemService { } } - protected List<String> getAllowedAssistantCapabilities() { + protected List<String> getAllowedAssistantAdjustments() { synchronized (mLock) { List<String> types = new ArrayList<>(); types.addAll(mAllowedAdjustments); @@ -7470,7 +7475,7 @@ public class NotificationManagerService extends SystemService { 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/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 714c2274af8e..9713befc91f6 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 @@ class TaskRecord extends ConfigurationContainer { 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 @@ class TaskRecord extends ConfigurationContainer { 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 6fe8b43db139..f31416cdc620 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 @@ class TaskSnapshotController { } 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 39b56625c011..5d99db5a00de 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 @@ package com.android.server.wm; 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 @@ class TaskSnapshotSurface implements StartingSurface { 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 @@ class TaskSnapshotSurface implements StartingSurface { 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 @@ class TaskSnapshotSurface implements StartingSurface { // 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 @@ class TaskSnapshotSurface implements StartingSurface { @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 @@ class TaskSnapshotSurface implements StartingSurface { 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 @@ class TaskSnapshotSurface implements StartingSurface { 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 @@ class TaskSnapshotSurface implements StartingSurface { 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/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index bafcd5f0f8bd..355ff63d18f7 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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"); @@ -4948,22 +4949,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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); @@ -4981,8 +4986,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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), @@ -4990,7 +4999,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 } @@ -5004,6 +5013,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.addAutomaticZenRule(rule); } + @Test public void testAreNotificationsEnabledForPackage_crossUser() throws Exception { try { mBinderService.areNotificationsEnabledForPackage(mContext.getPackageName(), @@ -5020,6 +5030,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { 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 ca815eca9a1f..cb6dc6d42bd5 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.any; 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.GraphicBuffer; 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 @@ public class TaskSnapshotSurfaceTest extends WindowTestsBase { 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/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 9c63a82b5673..0d3bc1db831f 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -3790,6 +3790,7 @@ public class TelephonyManager { * @hide * nobody seems to call this. */ + @TestApi @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getLine1AlphaTag() { return getLine1AlphaTag(getSubId()); diff --git a/tests/ProtoInputStreamTests/Android.mk b/tests/ProtoInputStreamTests/Android.mk new file mode 100644 index 000000000000..eb747cc2cdcc --- /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 000000000000..c11aa7393431 --- /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 000000000000..51ab88e4d4d0 --- /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 000000000000..cf9f0772ac2d --- /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 000000000000..c21c4033a0ff --- /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 000000000000..09fe40edda6c --- /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 000000000000..118fe3431e01 --- /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 000000000000..f55d95129588 --- /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 000000000000..df68476f0c36 --- /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 000000000000..af4130b28cd8 --- /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 000000000000..9bc07dc513e1 --- /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 000000000000..0065870486f2 --- /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 000000000000..4d6d105e60b0 --- /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 000000000000..5e49eeafb8af --- /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 000000000000..75c88a44614b --- /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 000000000000..4c65cf49318d --- /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 000000000000..6854cd8aad28 --- /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 000000000000..c53e9d72562a --- /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 000000000000..816d5f900a3d --- /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 000000000000..50fc537767a4 --- /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 000000000000..20969e9056a9 --- /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 000000000000..cdf6ae20f370 --- /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 000000000000..9ff1d7e2d19a --- /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; +}; |